Python packaging
A package is a build contract, not a folder of files. Declare metadata and
dependencies once in pyproject.toml, isolate importable code under src/,
and let a standard builder produce the artifacts you upload.
Method
- Make pyproject.toml the single source of truth. Put
[project]metadata (name, version,requires-python,dependencies,optional-dependencies) and a[build-system]table naming your backend (hatchling, setuptools, flit, or pdm). Deletesetup.pyandsetup.cfgunless a backend needs them; do not split config across files. - Adopt the src layout. Place the importable package at
src/mypkg/__init__.py. This forces tests to run against the installed package, not the working tree, so a missingpackage_dataor bad import fails locally instead of after publish. Configure the backend to findsrc. - Manage version deliberately. Either set
versionstatically and bump it per release, or usedynamic = ["version"]with a scheme that reads a git tag (hatch-vcs, setuptools-scm). Follow semantic versioning and never reuse a version number; PyPI rejects re-uploads of an existing version. - Declare entry points for executables and plugins. A console command
is
[project.scripts]mappingmytool = "mypkg.cli:main"; that installs a wrapper on PATH without a manual shebang. Plugin discovery uses[project.entry-points."group.name"]. Package a CLI this way rather than shipping a loose script; see python-cli-tools. - Build isolated artifacts. Run
python -m buildto produce both an sdist (.tar.gz) and a wheel (.whl) in a clean environment. Check them withtwine check dist/*and inspect the wheel contents to confirm no stray files or missing data. - Publish through an isolated token, TestPyPI first. Upload to TestPyPI
with
twine upload -r testpypi dist/*, install from there into a fresh venv, then upload to PyPI. Prefer a scoped API token or Trusted Publishing (OIDC from CI) over a password. Tag the release commit.
Boundaries
- This is library and tool packaging. Application deployment (Docker images, lockfile-pinned installs, reproducibility) belongs to python-environments.
- Compiled extensions need platform wheels and often cibuildwheel; a single pure-Python wheel will not cover them.
- Do not publish secrets: audit the sdist, since it includes files a wheel
omits and can leak
.envor credentials ifMANIFEST/excludes are wrong.