skip to content

Distribution and Library Layout

Turning source files into an installable artifact: project metadata, build backends, wheels, console scripts, and the public surface a library commits to. It separates library authors from script writers.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

page 2 of 2

When does a dependency belong in pyproject.toml's base `dependencies` versus an extra?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Anything the package cannot import or run its core path without belongs in base dependencies. Optional integrations, alternative backends and heavyweight accelerators belong in extras, guarded by an import that fails with the exact install command. Development tooling belongs in neither: it belongs in a dependency group.

open as a page

Why can `pip install mypkg[fast]` succeed while the extra installs nothing?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Because a request for an extra that resolves to nothing is not an error. An unknown or misspelled extra only produces a warning, environment markers on the extra's requirements can all evaluate false, and --no-deps or a stale cached wheel drops them entirely, while the command still exits zero.

open as a page

What does a wheel repair step such as auditwheel or delocate do to a freshly built extension wheel?

level: seniorimportance: should knowfreq 28%

basics

~20 s

It copies the external shared libraries the compiled extension links against into the wheel, rewrites the extension's library search paths so it loads the bundled copies, and mangles their names so they cannot collide with another package's copy of the same library.

open as a page

When do you need importlib.resources.as_file, and what does it cost a worker calling it 1,200 times a minute?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Use as_file when something outside Python needs a real filesystem path -- a subprocess argument, or a library that opens the file itself. It is a context manager: an archived resource is extracted per entry, so per-request use copies per request.

open as a page

What does the platform tag manylinux_2_28_x86_64 on a wheel promise, and which hosts can install it?

level: seniorimportance: should knowfreq 42%

basics

~20 s

It promises a Linux x86-64 binary that runs on any host whose glibc is 2.28 or newer, linking only against a small allowlist of system libraries. Older glibc, or musl instead of glibc, does not match and gets a source build.

open as a page

How do you retire a public function from a Python library without breaking callers?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Keep it working, make it warn. Emit a DeprecationWarning from the function with stacklevel=2 so the caller's line is blamed, document the replacement and the removal release, ship that for at least one release, then delete it in a major version.

open as a page

A PyPI release of your library makes a nightly report generator send every report twice — can you re-upload a fixed 1.4.2?

level: seniorimportance: should knowfreq 30%

basics

~20 s

No. PyPI accepts a given filename once and never again, and deleting it does not free the version number. Ship the fix as 1.4.3, then yank 1.4.2 so resolvers skip it while exact pins still install it.

open as a page

Why can a PEP 517 build fail on an import that works in your active virtualenv?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Because the build runs in a fresh, isolated environment containing only the [build-system] requires list. Your activated environment is not on the build's import path, so anything the backend needs must be declared, not merely installed nearby.

open as a page

Under a flat layout, how can a nightly rebuilder's tests pass while its installed wheel raises ModuleNotFoundError?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Because the tests never imported the wheel. With the repository root first on sys.path, a flat-layout test run imports the working copy, which contains every file; the built distribution can be missing a module and nothing in the run would notice.

open as a page

When should a library ship a stub-only distribution instead of py.typed?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Ship stubs when annotations cannot live in the code: a library you do not own, a compiled extension with no Python source, or a codebase that cannot carry them. If you own the source, inline annotations plus the marker win, because stubs drift.

open as a page

Why would pip suddenly build a dependency from its sdist when it previously installed a wheel in seconds?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Because no published wheel matched the environment's tags any more, so pip fell back to the source tarball. A new interpreter, a new base image or architecture, or a release that shipped no wheel for that platform all cause it.

open as a page

Your team ships an internal chat-transcript archiver library and other services now import helpers you never meant to expose. How do you decide what the public API promises, and how do you version those promises?

level: principalimportance: should knowfreq 30%

basics

~20 s

Make the surface small and explicit — underscore internals, list the promised names in __all__ at one import path — then treat every listed name as a versioned obligation. Where callers reached into internals, ask what the public API failed to offer before policing them.

open as a page

What does Python's zipapp module produce, and how does the interpreter run a .pyz archive?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

python -m zipapp zips a source directory containing a main.py into one .pyz file. CPython imports out of a zip placed on sys.path, so running the archive executes that main.py. The host still needs its own Python.

open as a page

When would you declare a command under [project.gui-scripts] instead of [project.scripts]?

level: middleimportance: nice to knowfreq 14%

basics

~20 s

For a desktop application launched from a shortcut rather than a terminal. On Windows the generated launcher is bound to the windowed interpreter, so no console window appears; on Linux and macOS the two tables produce the same wrapper.

open as a page

Why did PEP 735 add dependency groups when pyproject.toml already had extras?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

Extras are published metadata on a distribution, so a dev extra ships to every consumer and only works if the project is a package at all. PEP 735 dependency groups live in a top-level [dependency-groups] table, stay in the source tree, and never reach the built wheel.

open as a page

Your compiled package must ship wheels for Linux, macOS and Windows on x86-64 and arm64 — how do you build that matrix?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

Run one CI job per operating system and architecture, and inside each job loop the interpreter versions with a wheel-building tool that builds, repairs and smoke-tests each wheel. Use native runners where they exist, cross-compilation or emulation where they do not.

open as a page

What does the ABI tag cp314t on a wheel mean, and why won't a cp314 wheel work there?

level: seniorimportance: nice to knowfreq 20%

basics

~20 s

The trailing t marks the free-threaded CPython 3.14 build, which has its own binary interface. A compiled wheel tagged cp314 was built against the standard build's ABI, so it does not match and will not be installed by a free-threaded interpreter.

open as a page

showing 31–47 of 47