Episode 5: Building & DistributionΒΆ
Learning Objectives
By the end of this episode, you will:
- Build wheel and source distributions
- Understand semantic versioning
- Publish packages to PyPI
- Set up GitHub Actions for CI/CD
- Create basic documentation
- Understand package metadata and README rendering
π¬ Taking kir-pydemo to the WorldΒΆ
Sarah's kir-pydemo package is polished, tested, and ready! But how does she share it?
"I want colleagues at other institutions to use this. How do I get it on PyPI so they can just
pip install kir-pydemo? And how do I make sure each release is properly tested?"
The solution? Build distributions and set up automated publishing!
π¦ Understanding Package DistributionΒΆ
Types of DistributionsΒΆ
Python packages can be distributed in two formats:
1. Source Distribution (sdist)ΒΆ
A compressed archive (.tar.gz) of your source code:
kir-pydemo-0.1.0.tar.gz
βββ src/
β βββ kir_pydemo/
βββ tests/
βββ pyproject.toml
βββ README.md
βββ LICENSE
- Pros: Works on any platform, includes everything
- Cons: Requires build tools, slower to install
- When: Fallback when wheels aren't available
2. Wheel (.whl)ΒΆ
A pre-built package ready for installation:
- Pros: Fast installation, no build required
- Cons: May need platform-specific builds (for C extensions)
- When: Preferred format for distribution
The filename tells you:
kir_pydemo - 0.1.0 - py3 - none - any .whl
| | | | |
package version py3 no ABI universal
(pure Python)
ποΈ Building Your PackageΒΆ
Step 1: Install Build ToolsΒΆ
- build: Creates wheels and sdist
- twine: Uploads packages to PyPI
Step 2: Build the DistributionsΒΆ
This creates a dist/ directory:
Step 3: Check the BuildΒΆ
# Verify the distribution
twine check dist/*
# Output:
# Checking dist/kir_pydemo-0.1.0-py3-none-any.whl: PASSED
# Checking dist/kir_pydemo-0.1.0.tar.gz: PASSED
Step 4: Test Install LocallyΒΆ
π’ Semantic VersioningΒΆ
Version numbers communicate compatibility and changes:
MAJOR.MINOR.PATCH
| | |
| | ββ Bug fixes (backwards compatible)
| ββββββββ New features (backwards compatible)
ββββββββββββββ Breaking changes (NOT backwards compatible)
ExamplesΒΆ
0.1.0β0.1.1: Fixed a bug0.1.1β0.2.0: Added new feature (reverse_complement)0.2.0β1.0.0: First stable release1.0.0β2.0.0: Changed API (breaking change)
Pre-releasesΒΆ
1.0.0a1 # Alpha release 1
1.0.0b1 # Beta release 1
1.0.0rc1 # Release candidate 1
1.0.0 # Final release
Version in pyproject.tomlΒΆ
Static version:
Dynamic version (from code):
[project]
dynamic = ["version"]
[tool.setuptools.dynamic]
version = {attr = "kir_pydemo.__version__"}
Then in src/kir_pydemo/__init__.py:
Version Management Tools
- bump2version: CLI tool to bump versions
- poetry version: Poetry's version management
- setuptools_scm: Git tag-based versioning
Example with setuptools_scm:
π Publishing to PyPIΒΆ
Test on TestPyPI FirstΒΆ
TestPyPI is a separate instance for testing:
# Create account on https://test.pypi.org/account/register/
# Upload to TestPyPI
twine upload --repository testpypi dist/*
# Install from TestPyPI to verify
pip install --index-url https://test.pypi.org/simple/ kir-pydemo
Publish to PyPIΒΆ
# Create account on https://pypi.org/account/register/
# Upload to PyPI
twine upload dist/*
# Enter your credentials or use API token
Package Name Availability
Check if the name is available first:
Visit https://pypi.org/project/ to check.Using API Tokens (Recommended)ΒΆ
Create an API token on PyPI:
- Go to https://pypi.org/manage/account/
- Create a new API token
- Store it safely
Option 1: .pypirc file
Create ~/.pypirc:
[pypi]
username = __token__
password = pypi-AgEIcHlwaS5vcmc...
[testpypi]
username = __token__
password = pypi-AgENdGVzdC5weXBp...
Option 2: Environment variable
π€ Continuous Integration with GitHub ActionsΒΆ
Automate testing and publishing with GitHub Actions:
Step 1: Create Workflow DirectoryΒΆ
Step 2: Create CI WorkflowΒΆ
Create .github/workflows/ci.yml:
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.9', '3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Lint with ruff
run: |
ruff check src/ tests/
- name: Type check with mypy
run: |
mypy src/
- name: Test with pytest
run: |
pytest --cov=kir_pydemo --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
fail_ci_if_error: true
Step 3: Create Release WorkflowΒΆ
Create .github/workflows/release.yml:
name: Release
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Build package
run: python -m build
- name: Check package
run: twine check dist/*
- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
twine upload dist/*
Step 4: Add PyPI Token to GitHubΒΆ
- Generate API token on PyPI
- Go to your GitHub repo β Settings β Secrets and variables β Actions
- Add new secret:
PYPI_API_TOKEN
Now when you create a release on GitHub, it automatically publishes to PyPI!
π Complete Checklist for ReleaseΒΆ
Before publishing your package:
Code QualityΒΆ
- All tests passing
- Code coverage >80%
- No linting errors
- Type checking passes
- Pre-commit hooks configured
DocumentationΒΆ
- Comprehensive README.md
- Usage examples
- Installation instructions
- API documentation
- Changelog or release notes
MetadataΒΆ
- Appropriate version number
- Correct dependencies and version constraints
- Proper license
- Keywords and classifiers
- Project URLs (homepage, docs, issues)
TestingΒΆ
- Tested on multiple Python versions
- Tested on different platforms (if relevant)
- Test installation from built wheel
SecurityΒΆ
- No sensitive data in code
- Dependencies checked for vulnerabilities
- API tokens secured (not in git)
LegalΒΆ
- LICENSE file included
- All code properly attributed
- Dependencies' licenses compatible
π Checkpoint: What Have We Achieved?ΒΆ
Verify you've successfully completed Episode 5:
- Built wheel and source distributions with
python -m build - Verified distributions with
twine check - Understand semantic versioning (MAJOR.MINOR.PATCH)
- Published to TestPyPI successfully
- Set up GitHub Actions for CI
- Created comprehensive README with badges
- Configured automated release workflow
π― Key TakeawaysΒΆ
- Two distribution formats: Wheels (preferred) and source distributions (fallback)
- Semantic versioning communicates compatibility: MAJOR.MINOR.PATCH
- Test on TestPyPI before publishing to PyPI
- Use API tokens for secure authentication
- GitHub Actions automate testing and publishing
- Good documentation is crucial for adoption
- Release checklist ensures quality releases
π Series Wrap-UpΒΆ
Congratulations! You've completed the Python Packaging Basics series. You now know how to:
β
Structure packages with modern practices (src/ layout, pyproject.toml)
β
Build CLI tools with entry points
β
Manage dependencies and environments
β
Test and maintain code quality
β
Distribute packages to PyPI
What You've BuiltΒΆ
The kir-pydemo package now:
- Has a clean, modern project structure
- Provides both Python API and CLI
- Manages dependencies properly
- Includes comprehensive tests
- Follows code quality standards
- Can be published to PyPI
- Has automated CI/CD
Next StepsΒΆ
- Apply to your projects: Package your own tools
- Explore advanced topics: C extensions, binary wheels, conda packages
- Join the community: Contribute to open source
- Keep learning: Python packaging evolves - stay updated!