# Publishing Python Packages Publish packages to PyPI with modern tooling. ## pyproject.toml for Publishing ```toml [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 ```bash # 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 ```toml [project] version = "0.1.0" ``` ### Option 2: Dynamic from __init__.py ```toml [project] dynamic = ["version"] [tool.hatch.version] path = "src/my_package/__init__.py" ``` ```python # src/my_package/__init__.py __version__ = "0.1.0" ``` ### Option 3: Git tags with hatch-vcs ```toml [project] dynamic = ["version"] [tool.hatch.version] source = "vcs" [build-system] requires = ["hatchling", "hatch-vcs"] build-backend = "hatchling.build" ``` ```bash # 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) ```markdown # 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 ```yaml # .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) ```ini [pypi] username = __token__ password = pypi-xxxx... [testpypi] repository = https://test.pypi.org/legacy/ username = __token__ password = pypi-xxxx... ``` ### Trusted Publishing (Recommended) 1. Go to PyPI → Your project → Publishing 2. Add new trusted publisher 3. Set GitHub repo and workflow file 4. 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 ```markdown - [ ] 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 ```