Comprehensive guide for publishing Python packages to PyPI...
Complete workflow for publishing Python packages to PyPI with best practices, error handling, and automation.
ALWAYS run tests before publishing. Never skip this step.
Run full test suite
python -m pytest tests/ -v --tb=short
Verify version numbers are consistent
pyproject.toml: version = "X.Y.Z"package/__init__.py: __version__ = "X.Y.Z"mcp_server.py server_version)Check for uncommitted changes
Clean old build artifacts
# Windows PowerShell
Remove-Item -Recurse -Force dist, build, *.egg-info -ErrorAction SilentlyContinue
# Linux/Mac
rm -rf dist/ build/ *.egg-info
uv build
# or
python -m build
Verify output:
dist/package-name-X.Y.Z.tar.gz (source distribution)dist/package_name-X.Y.Z-py3-none-any.whl (wheel)uvx twine check dist/*
Must pass before uploading. Fix any warnings or errors.
Test PyPI (recommended for first-time publishing):
uvx twine upload --repository testpypi dist/*
Production PyPI:
uvx twine upload dist/*
Using API Token (recommended):
Windows PowerShell:
$env:TWINE_USERNAME = "__token__"
$env:TWINE_PASSWORD = "pypi-your-api-token-here"
uvx twine upload dist/* --non-interactive
Linux/Mac:
export TWINE_USERNAME=__token__
export TWINE_PASSWORD=pypi-your-api-token-here
uvx twine upload dist/* --non-interactive
Create API Token:
__token__Cause: Version already published to PyPI.
Solution:
pyproject.toml and __init__.pyVersion numbering:
0.1.0 β 0.1.1 (bug fixes)0.1.0 β 0.2.0 (new features, backward compatible)0.1.0 β 1.0.0 (breaking changes)Cause: Missing authentication credentials.
Solution:
TWINE_USERNAME and TWINE_PASSWORD environment variables--non-interactive flag with credentials~/.pypirc config fileCause: Package metadata or structure issues.
Solution:
twine check dist/* to see specific errorspyproject.tomlCause: Package structure or import paths incorrect.
Solution:
[tool.hatch.build.targets.wheel] packages configuration__init__.py files existpip install -e .For PowerShell (Windows):
# Run tests first
python -m pytest tests/ -v --tb=short
if ($LASTEXITCODE -ne 0) {
Write-Host "Tests failed! Aborting publish." -ForegroundColor Red
exit 1
}
# Build
uv build
if ($LASTEXITCODE -ne 0) { exit 1 }
# Check
uvx twine check dist/*
if ($LASTEXITCODE -ne 0) { exit 1 }
# Upload (set credentials first)
$env:TWINE_USERNAME = "__token__"
$env:TWINE_PASSWORD = "your-token"
uvx twine upload dist/* --non-interactive
Always update version in these locations:
pyproject.toml: version = "X.Y.Z"package/__init__.py: __version__ = "X.Y.Z"Semantic versioning:
Check PyPI page:
https://pypi.org/project/your-package-name/X.Y.Z/
Test installation:
pip install your-package-name==X.Y.Z
Verify functionality:
Always test before publishing
Use Test PyPI first
Use API tokens, not passwords
Version consistently
Automate what you can
Handle errors gracefully
Essential commands:
# Test
python -m pytest tests/ -v
# Build
uv build
# Check
uvx twine check dist/*
# Upload (Test PyPI)
uvx twine upload --repository testpypi dist/*
# Upload (Production)
uvx twine upload dist/*
Version update locations:
pyproject.tomlpackage/__init__.pyCommon file patterns:
dist/*.whl - Wheel distributiondist/*.tar.gz - Source distributionbuild/ - Temporary build files (can delete)*.egg-info/ - Package metadata (can delete)Pre-built scripts are available in scripts/:
publish.ps1 - PowerShell script for Windows
.\publish.ps1 --test # Publish to Test PyPI
.\publish.ps1 --token "pypi-xxx" # Publish to Production with token
publish.sh - Bash script for Linux/Mac
chmod +x publish.sh
./publish.sh --test # Publish to Test PyPI
./publish.sh --token "pypi-xxx" # Publish to Production with token
These scripts automatically: