Publishing Python Packages
Publish packages to PyPI with modern tooling.
pyproject.toml for Publishing
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my-package"
version = "0.1.0"
description = "My awesome package"
readme = "README.md"
license = {file = "LICENSE"}
authors = [
{name = "Your Name", email = "you@example.com"},
]
keywords = ["keyword1", "keyword2"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
requires-python = ">=3.10"
dependencies = [
"requests>=2.28",
"pydantic>=2.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"pytest-cov>=4.0",
"ruff>=0.1",
"mypy>=1.0",
]
[project.urls]
Homepage = "https://github.com/username/my-package"
Documentation = "https://my-package.readthedocs.io"
Repository = "https://github.com/username/my-package"
Changelog = "https://github.com/username/my-package/blob/main/CHANGELOG.md"
[project.scripts]
my-command = "my_package.cli:main"
[project.entry-points."my_package.plugins"]
plugin1 = "my_package.plugins:Plugin1"
Build and Upload
# Install build tools
uv pip install build twine
# Build package
python -m build
# Check build artifacts
ls dist/
# my_package-0.1.0-py3-none-any.whl
# my_package-0.1.0.tar.gz
# Check distribution
twine check dist/*
# Upload to TestPyPI first
twine upload --repository testpypi dist/*
# Test installation from TestPyPI
uv pip install --index-url https://test.pypi.org/simple/ my-package
# Upload to PyPI (production)
twine upload dist/*
Version Management
Option 1: Manual version
[project]
version = "0.1.0"
Option 2: Dynamic from init.py
[project]
dynamic = ["version"]
[tool.hatch.version]
path = "src/my_package/__init__.py"
# src/my_package/__init__.py
__version__ = "0.1.0"
Option 3: Git tags with hatch-vcs
[project]
dynamic = ["version"]
[tool.hatch.version]
source = "vcs"
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
# Create version tag
git tag -a v0.1.0 -m "Release 0.1.0"
git push origin v0.1.0
Semantic Versioning
MAJOR.MINOR.PATCH
Examples:
0.1.0 - Initial development
0.2.0 - New features (minor)
0.2.1 - Bug fixes (patch)
1.0.0 - First stable release
1.1.0 - New features, backwards compatible
2.0.0 - Breaking changes
Changelog (CHANGELOG.md)
# Changelog
All notable changes to this project will be documented in this file.
## [Unreleased]
### Added
- New feature X
### Changed
- Updated dependency Y
### Fixed
- Bug in Z
## [0.1.0] - 2024-01-15
### Added
- Initial release
- Core functionality
[Unreleased]: https://github.com/user/repo/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/user/repo/releases/tag/v0.1.0
GitHub Actions CI/CD
# .github/workflows/publish.yml
name: Publish to PyPI
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write # For trusted publishing
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install build tools
run: pip install build
- name: Build package
run: python -m build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
# Uses trusted publishing - no token needed
PyPI Configuration
~/.pypirc (for twine)
[pypi]
username = __token__
password = pypi-xxxx...
[testpypi]
repository = https://test.pypi.org/legacy/
username = __token__
password = pypi-xxxx...
Trusted Publishing (Recommended)
- Go to PyPI → Your project → Publishing
- Add new trusted publisher
- Set GitHub repo and workflow file
- No API token needed in CI
Source Distribution Layout
my-package/
├── pyproject.toml
├── README.md
├── LICENSE
├── CHANGELOG.md
├── src/
│ └── my_package/
│ ├── __init__.py
│ └── core.py
└── tests/
└── test_core.py
Quick Reference
| Command |
Purpose |
python -m build |
Build wheel and sdist |
twine check dist/* |
Verify package |
twine upload dist/* |
Upload to PyPI |
twine upload --repository testpypi dist/* |
Upload to TestPyPI |
| Version |
When |
| 0.x.x |
Initial development |
| x.0.0 |
Breaking changes |
| x.x.0 |
New features |
| x.x.x |
Bug fixes |
Checklist Before Publishing
- [ ] Version updated in pyproject.toml
- [ ] CHANGELOG.md updated
- [ ] README.md current
- [ ] All tests passing
- [ ] Type checks passing
- [ ] Build succeeds locally
- [ ] TestPyPI upload works
- [ ] Installation from TestPyPI works