Python Package Distribution: Sharing Your Code
Welcome to Package Distribution! Think of this as publishing your book - making your code available for others to install and use. Python has excellent tools for creating distributable packages.
Why Distribute Packages?
Package distribution allows you:
- Share code with other developers
- Install easily with
pip install - Manage dependencies automatically
- Version control releases
- Build communities around your code
Distribution Methods
1. Source Distribution (sdist)
Contains source code that can be installed on any platform:
my_package-1.0.0.tar.gz
├── my_package-1.0.0/
│ ├── setup.py
│ ├── my_package/
│ │ ├── __init__.py
│ │ └── core.py
│ └── README.md
2. Built Distribution (wheel)
Pre-compiled binary distribution (faster installation):
my_package-1.0.0-py3-none-any.whl
├── my_package/
│ ├── __init__.py
│ └── core.py
└── metadata/
3. Platform Wheels
Platform-specific binaries for compiled extensions:
my_package-1.0.0-cp39-cp39-win_amd64.whl # Windows
my_package-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl # macOS
my_package-1.0.0-cp39-cp39-linux_x86_64.whl # Linux
Creating Your First Package
Step 1: Project Structure
my_package/
├── src/
│ └── my_package/
│ ├── __init__.py
│ ├── core.py
│ └── utils.py
├── tests/
│ ├── __init__.py
│ └── test_core.py
├── docs/
├── pyproject.toml
├── README.md
├── LICENSE
└── .gitignore
Step 2: pyproject.toml Configuration
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
version = "1.0.0"
description = "A useful Python package"
readme = "README.md"
license = {file = "LICENSE"}
requires-python = ">=3.8"
authors = [
{name = "Your Name", email = "your.email@example.com"},
]
maintainers = [
{name = "Your Name", email = "your.email@example.com"},
]
keywords = ["useful", "package", "python"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
]
dependencies = [
"requests>=2.25.0",
"click>=8.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"pytest-cov>=4.0",
"black>=22.0",
"flake8>=5.0",
"mypy>=1.0",
]
docs = [
"sphinx>=5.0",
"sphinx-rtd-theme>=1.0",
]
test = [
"pytest>=7.0",
"pytest-cov>=4.0",
]
[project.scripts]
my-command = "my_package.cli:main"
[project.urls]
Homepage = "https://github.com/yourusername/my-package"
Documentation = "https://my-package.readthedocs.io/"
Repository = "https://github.com/yourusername/my-package.git"
Issues = "https://github.com/yourusername/my-package/issues"
Changelog = "https://github.com/yourusername/my-package/blob/main/CHANGELOG.md"
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
my_package = ["*.txt", "*.md"]
[tool.black]
line-length = 88
target-version = ['py38']
[tool.pytest.ini_options]
minversion = "7.0"
addopts = "-ra -q --cov=my_package"
testpaths = ["tests"]
[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
Step 3: Package Code
# src/my_package/__init__.py
"""My Package - A useful Python package."""
__version__ = "1.0.0"
__author__ = "Your Name"
__email__ = "your.email@example.com"
from .core import main_function
from .utils import helper_function
__all__ = ["main_function", "helper_function"]
# src/my_package/core.py
"""Core functionality."""
def main_function(name: str) -> str:
"""Return a greeting message."""
return f"Hello, {name}!"
def calculate_sum(*args: float) -> float:
"""Calculate the sum of numbers."""
return sum(args)
# src/my_package/utils.py
"""Utility functions."""
def helper_function(text: str) -> str:
"""Convert text to uppercase."""
return text.upper()
def validate_email(email: str) -> bool:
"""Validate email address format."""
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))
Step 4: README.md
# My Package
[](https://pypi.org/project/my-package/)
[](https://pypi.org/project/my-package/)
[](https://pypi.org/project/my-package/)
A useful Python package that provides core functionality and utilities.
## Installation
```bash
pip install my-package
Usage
import my_package
# Use main function
result = my_package.main_function("World")
print(result) # "Hello, World!"
# Use utilities
upper = my_package.helper_function("hello")
print(upper) # "HELLO"
Development
# Clone the repository
git clone https://github.com/yourusername/my-package.git
cd my-package
# Install development dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Format code
black src/ tests/
# Type checking
mypy src/
Contributing
Contributions are welcome! Please see our Contributing Guide for details.
License
This project is licensed under the MIT License - see the LICENSE file for details.
### Step 5: Build the Package
```bash
# Install build tools
pip install build
# Build distributions
python -m build
# This creates:
# dist/
# ├── my_package-1.0.0.tar.gz # Source distribution
# └── my_package-1.0.0-py3-none-any.whl # Built distribution
Step 6: Test Installation
# Test in a virtual environment
python -m venv test_env
test_env\Scripts\activate # Windows
# source test_env/bin/activate # macOS/Linux
# Install from local build
pip install dist/my_package-1.0.0-py3-none-any.whl
# Test the package
python -c "import my_package; print(my_package.main_function('test'))"
Publishing to PyPI
TestPyPI (Testing)
# Install twine for uploading
pip install twine
# Upload to TestPyPI
twine upload --repository testpypi dist/*
# Install from TestPyPI
pip install --index-url https://test.pypi.org/simple/ my-package
Production PyPI
# Upload to real PyPI
twine upload dist/*
# Install from PyPI
pip install my-package
Advanced Distribution Features
Entry Points (Console Scripts)
[project.scripts]
my-command = "my_package.cli:main"
hello-world = "my_package.cli:hello_command"
# src/my_package/cli.py
import click
@click.group()
def main():
"""My Package CLI"""
pass
@main.command()
@click.argument('name')
def hello(name):
"""Say hello to someone."""
from .core import main_function
click.echo(main_function(name))
@main.command()
def version():
"""Show package version."""
from . import __version__
click.echo(f"My Package v{__version__}")
if __name__ == "__main__":
main()
Plugin System with Entry Points
[project.entry-points."my_package.plugins"]
csv_plugin = "my_package.plugins.csv_plugin:CSVPlugin"
json_plugin = "my_package.plugins.json_plugin:JSONPlugin"
# src/my_package/plugins.py
import importlib.metadata
def load_plugins():
"""Load all registered plugins."""
plugins = {}
for entry_point in importlib.metadata.entry_points(group="my_package.plugins"):
plugin_class = entry_point.load()
plugins[entry_point.name] = plugin_class()
return plugins
Including Data Files
[tool.setuptools.package-data]
my_package = ["data/*.json", "templates/*.html", "static/*"]
Namespace Packages
For packages split across distributions:
# No __init__.py in namespace root
my_namespace/
├── package_a/
│ ├── __init__.py
│ └── ...
└── package_b/
├── __init__.py
└── ...
Version Management
Version Schemes
- Semantic Versioning:
MAJOR.MINOR.PATCH(1.2.3) - Calendar Versioning:
YEAR.MONTH.PATCH(2023.5.1) - Development Versions:
1.0.0.dev0,1.0.0a1
Automatic Versioning
# src/my_package/__init__.py
try:
from importlib.metadata import version
__version__ = version("my-package")
except ImportError:
# Fallback for older Python
__version__ = "unknown"
Version Files
# _version.py
__version__ = "1.0.0"
# __init__.py
from ._version import __version__
Dependency Management
Core vs Optional Dependencies
[project]
dependencies = [
"requests>=2.25.0", # Always required
]
[project.optional-dependencies]
pandas = ["pandas>=1.3.0"] # Optional
plotting = ["matplotlib>=3.5.0", "seaborn>=0.11.0"]
all = ["my-package[pandas,plotting]"] # All optional
Conditional Dependencies
# setup.py (legacy)
import sys
install_requires = ["requests>=2.25.0"]
if sys.version_info < (3, 9):
install_requires.append("importlib-metadata")
setup(
name="my-package",
install_requires=install_requires,
# ...
)
Testing Distributions
tox for Multi-Environment Testing
# tox.ini
[tox]
envlist = py38, py39, py310, py311
[testenv]
deps = pytest
commands = pytest tests/
# Run tests on all Python versions
tox
CI/CD with GitHub Actions
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3
- 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: Run tests
run: pytest --cov=my_package
- name: Upload coverage
uses: codecov/codecov-action@v3
Common Issues and Solutions
Import Errors After Installation
Problem: Package installs but imports fail
Solutions:
- Check
pyproject.tomlpackage discovery - Ensure proper
__init__.pyfiles - Verify import structure matches package structure
Missing Dependencies
Problem: Installation fails due to missing build dependencies
Solution:
[build-system]
requires = ["setuptools>=61.0", "wheel", "cython>=0.29.0"] # Add missing deps
build-backend = "setuptools.build_meta"
Platform-Specific Issues
Problem: Package works on one platform but not others
Solutions:
- Use platform-specific dependencies
- Provide platform-specific wheels
- Test on multiple platforms in CI
Version Conflicts
Problem: Package conflicts with installed versions
Solutions:
- Use version ranges carefully
- Test with different dependency versions
- Use virtual environments
Distribution Best Practices
1. Use Semantic Versioning
- MAJOR: Breaking changes
- MINOR: New features (backward compatible)
- PATCH: Bug fixes (backward compatible)
2. Test Before Publishing
# Test build
python -m build
# Test installation
pip install dist/*.whl --force-reinstall
# Test imports
python -c "import my_package; print('Import successful')"
# Test functionality
python -c "import my_package; my_package.main_function('test')"
3. Use Classifiers
classifiers = [
"Development Status :: 4 - Beta", # Status
"Intended Audience :: Developers", # Audience
"License :: OSI Approved :: MIT License", # License
"Programming Language :: Python :: 3", # Python versions
"Topic :: Software Development :: Libraries :: Python Modules", # Topic
]
4. Provide Comprehensive Metadata
[project]
# Basic info
name = "my-package"
version = "1.0.0"
description = "One-line description"
readme = "README.md"
# URLs
[project.urls]
Homepage = "https://my-package.com"
Documentation = "https://docs.my-package.com"
Repository = "https://github.com/user/my-package"
Issues = "https://github.com/user/my-package/issues"
5. Include License and Changelog
LICENSE # Full license text
CHANGELOG.md # Version history
CONTRIBUTING.md # How to contribute
Practice Exercises
Exercise 1: Create a Distributable Package
Create a complete package with:
- Core functionality
- CLI interface
- Tests
- Documentation
- Proper configuration
- Publish to TestPyPI
Exercise 2: Plugin Architecture
Build a package with:
- Plugin system using entry points
- Multiple plugin implementations
- Plugin discovery and loading
- Example plugins included
Exercise 3: Multi-Platform Package
Create a package that:
- Works on Windows, macOS, and Linux
- Includes platform-specific features
- Provides fallbacks for missing features
- Tests on multiple platforms
Exercise 4: Version Management
Implement automatic versioning with:
- Git tags for releases
- Development versions
- Version checking
- Changelog generation
Summary
Package distribution makes your code shareable and installable:
Key Components:
pyproject.toml- Modern package configuration- Source and wheel distributions
- Entry points for CLI commands
- Optional dependencies
- Comprehensive metadata
Publishing Process:
- Build:
python -m build - Test: Install locally and test
- Upload:
twine upload dist/* - Verify: Install from PyPI and test
Best Practices:
- Use semantic versioning
- Test on multiple platforms
- Provide clear documentation
- Include comprehensive metadata
- Use proper classifiers and licenses
Tools:
- build: Create distributions
- twine: Upload to PyPI
- setuptools: Package building
- pip: Installation testing
Congratulations! You’ve completed the Modules and Packages module. Your Python code is now properly organized, modular, and distributable! 🎉