Packaging Best Practices
Rules for packages that install cleanly, version predictably, and publish safely.
How to Use This List
- Apply when creating a new package or preparing a release
- Libraries follow stricter rules than internal applications
- Review before every PyPI publish
A - Project Structure
- src/ layout. Tests import the installed package.
- pyproject.toml only. No setup.py, setup.cfg, or requirements.txt as source of truth.
- hatchling build backend. Unless extensions require maturin/setuptools.
- README on PyPI.
readme = "README.md"in[project].
B - Metadata
- Clear name and description. Searchable on PyPI.
- requires-python set. Match actual supported versions.
- License declared. SPDX identifier in
[project]. - Classifiers accurate. Python version, OS, framework tags.
C - Versioning & Releases
- SemVer for libraries. MAJOR for breaking, MINOR for features, PATCH for fixes.
- CHANGELOG updated. Every release documented.
- Git tag matches version.
v1.2.3tag for1.2.3release. - TestPyPI first. Verify install before production PyPI.
D - Build & Publish
- Build in CI. Reproducible artifacts from clean environment.
- Test the wheel.
pip install dist/*.whlin fresh venv before upload. - Trusted publishing. OIDC tokens, not passwords in CI.
- Both wheel and sdist. Publish both formats.
E - Security
- No secrets in package. Tokens, keys, and passwords never in source or metadata.
- Minimal dependencies. Fewer deps = smaller attack surface.
- Yank, don't delete. Bad releases yanked on PyPI with explanation.
- 2FA on PyPI account. Required for all publishers.
FAQs
Library or application packaging?
Libraries: PyPI with semver. Applications: Docker or pipx.
Should I publish to PyPI?
If other teams or the community install it. Internal-only: private index.
How do I choose a package name?
Check pypi.org for availability. Lowercase, hyphens, no typosquatting.
What license?
MIT or Apache-2.0 for most open source. Legal review for proprietary.
Include tests in sdist?
Yes for sdist. Tests not installed with wheel (exclude via config).
How do I deprecate a package?
README notice, PyPI classifier Development Status :: 7 - Inactive, final release.
Monorepo publishing?
Independent versions per package. Separate PyPI projects.
Signing releases?
PyPI supports PGP-signed attestations. Trusted publishing is the modern default.
How often to release?
When meaningful changes accumulate. Patch releases for security fixes immediately.
What about data files?
Declare in build config. Access via importlib.resources at runtime.
Related
- Packaging Basics - getting started
- Publishing to PyPI - upload workflow
- Versioning & Changelogs - release notes
- Environments Best Practices - dependency management
Stack versions: This page was written for Python 3.14.0, FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.