$ cat /posts/creating-python-packages-distribution-and-publishing.md
[tags]Python

Creating Python Packages: Distribution and Publishing

drwxr-xr-x2026-01-185 min0 views
Creating Python Packages: Distribution and Publishing

Python package creation transforms reusable code into distributable libraries enabling sharing functionality across projects and with broader Python community through PyPI (Python Package Index). Creating packages involves proper directory structure organizing modules, metadata configuration with setup.py or pyproject.toml defining package information, building distributions generating wheel and source archives, and publishing to PyPI making packages installable via pip. Professional package distribution enables code reuse, establishes open-source contributions, builds reputation, and provides versioned releases maintaining backward compatibility.

This comprehensive guide explores package structure organizing source code in proper hierarchy with __init__.py marking directories as packages, README providing documentation, LICENSE defining usage terms, and tests ensuring quality, configuration files including setup.py defining metadata with name, version, author, dependencies using setuptools, pyproject.toml replacing setup.py following PEP 517/518 standards, and setup.cfg for static configuration, building distributions using python setup.py sdist creating source distributions as tar.gz archives, bdist_wheel generating wheel files for faster installation, and build module following modern standards, version management with semantic versioning using MAJOR.MINOR.PATCH format indicating compatibility, __version__ attributes, and version bump strategies, publishing to PyPI creating accounts on pypi.org and test.pypi.org, using twine uploading distributions securely with authentication, configuring .pypirc storing credentials, and uploading with twine upload, package metadata including long descriptions from README, classifiers categorizing packages by development status, audience, and topics, keywords enabling discovery, and dependencies specifying install_requires and extras_require. Whether you're sharing utility functions creating helper libraries, building frameworks developing reusable components, distributing tools providing command-line utilities, contributing open-source releasing public packages, or managing internal packages hosting private repositories, mastering Python package creation provides essential skills for professional code distribution enabling efficient sharing, versioned releases, dependency management, and community collaboration supporting software development workflows and open-source ecosystem participation.

Package Structure and Organization

Proper package structure organizes code, documentation, tests, and metadata following Python conventions. The standard layout includes source code in package directories, configuration files at project root, documentation explaining usage, and tests validating functionality. Understanding structure conventions enables creating professional maintainable packages.

pythonpackage_structure.py
# Package Structure and Organization

"""
Standard Python Package Structure:

mypackage/
โ”œโ”€โ”€ mypackage/              # Main package directory
โ”‚   โ”œโ”€โ”€ __init__.py        # Package initialization
โ”‚   โ”œโ”€โ”€ core.py            # Core functionality
โ”‚   โ”œโ”€โ”€ utils.py           # Utility functions
โ”‚   โ””โ”€โ”€ subpackage/        # Sub-package
โ”‚       โ”œโ”€โ”€ __init__.py
โ”‚       โ””โ”€โ”€ module.py
โ”œโ”€โ”€ tests/                 # Test directory
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ test_core.py
โ”‚   โ””โ”€โ”€ test_utils.py
โ”œโ”€โ”€ docs/                  # Documentation
โ”‚   โ”œโ”€โ”€ index.md
โ”‚   โ””โ”€โ”€ api.md
โ”œโ”€โ”€ examples/              # Usage examples
โ”‚   โ””โ”€โ”€ example_usage.py
โ”œโ”€โ”€ setup.py               # Package configuration (legacy)
โ”œโ”€โ”€ pyproject.toml         # Modern package configuration
โ”œโ”€โ”€ setup.cfg              # Static configuration
โ”œโ”€โ”€ README.md              # Project description
โ”œโ”€โ”€ LICENSE                # License file
โ”œโ”€โ”€ MANIFEST.in            # Include additional files
โ”œโ”€โ”€ requirements.txt       # Development dependencies
โ”œโ”€โ”€ .gitignore             # Git ignore rules
โ””โ”€โ”€ CHANGELOG.md           # Version history
"""

# === mypackage/__init__.py ===

"""
Package initialization file.
Defines what's exported when package is imported.
"""

# Package metadata
__version__ = "1.0.0"
__author__ = "Your Name"
__email__ = "[email protected]"
__license__ = "MIT"

# Import main components for easy access
from .core import MainClass, main_function
from .utils import helper_function, validate_input

# Define public API
__all__ = [
    'MainClass',
    'main_function',
    'helper_function',
    'validate_input'
]

# === mypackage/core.py ===

"""
Core functionality of the package.
"""

class MainClass:
    """Main class providing primary functionality."""
    
    def __init__(self, config=None):
        """Initialize with optional configuration."""
        self.config = config or {}
    
    def process(self, data):
        """Process data and return result."""
        # Processing logic
        return f"Processed: {data}"

def main_function(arg1, arg2):
    """
    Main function for package.
    
    Args:
        arg1: First argument
        arg2: Second argument
    
    Returns:
        Processed result
    """
    return f"Result: {arg1} + {arg2}"

# === mypackage/utils.py ===

"""
Utility functions.
"""

def helper_function(value):
    """
    Helper function.
    
    Args:
        value: Input value
    
    Returns:
        Transformed value
    """
    return value * 2

def validate_input(data):
    """
    Validate input data.
    
    Args:
        data: Data to validate
    
    Raises:
        ValueError: If data is invalid
    
    Returns:
        bool: True if valid
    """
    if not data:
        raise ValueError("Data cannot be empty")
    return True

# === mypackage/subpackage/__init__.py ===

"""
Sub-package initialization.
"""

from .module import SubClass

__all__ = ['SubClass']

# === mypackage/subpackage/module.py ===

"""
Sub-package module.
"""

class SubClass:
    """Sub-package class."""
    
    def __init__(self):
        pass
    
    def sub_method(self):
        """Sub-package method."""
        return "Sub-package result"

# === tests/test_core.py ===

"""
Tests for core functionality.
"""

import unittest
from mypackage.core import MainClass, main_function

class TestMainClass(unittest.TestCase):
    """Test cases for MainClass."""
    
    def setUp(self):
        """Set up test fixtures."""
        self.instance = MainClass()
    
    def test_process(self):
        """Test process method."""
        result = self.instance.process("test data")
        self.assertIn("Processed", result)
    
    def test_main_function(self):
        """Test main function."""
        result = main_function("a", "b")
        self.assertIn("Result", result)

if __name__ == '__main__':
    unittest.main()

# === examples/example_usage.py ===

"""
Example usage of the package.
"""

from mypackage import MainClass, main_function
from mypackage.utils import helper_function

# Create instance
obj = MainClass(config={'setting': 'value'})

# Use functionality
result = obj.process("sample data")
print(result)

# Use function
output = main_function("hello", "world")
print(output)

# Use utility
value = helper_function(10)
print(value)

print("Package structure examples completed!")
Always Include __init__.py: Every package directory needs __init__.py file (even if empty) marking it as Python package. Controls what's imported.

Configuration Files and Metadata

Configuration files define package metadata, dependencies, and build instructions. Modern Python packaging uses pyproject.toml following PEP 517/518 standards, though setup.py remains common. Metadata includes package name, version, author information, dependencies, and classifiers enabling proper distribution and installation.

pythonconfiguration_files.py
# Configuration Files and Metadata

# === setup.py (Traditional approach) ===

"""
setup.py - Package configuration using setuptools.
"""

from setuptools import setup, find_packages
import os

# Read README for long description
with open('README.md', 'r', encoding='utf-8') as f:
    long_description = f.read()

# Read version from package
with open('mypackage/__init__.py', 'r') as f:
    for line in f:
        if line.startswith('__version__'):
            version = line.split('=')[1].strip().strip('"\'')
            break

setup(
    # Basic metadata
    name='mypackage',
    version=version,
    author='Your Name',
    author_email='[email protected]',
    description='A short description of your package',
    long_description=long_description,
    long_description_content_type='text/markdown',
    url='https://github.com/username/mypackage',
    
    # Package discovery
    packages=find_packages(exclude=['tests*', 'docs*']),
    
    # Dependencies
    install_requires=[
        'requests>=2.25.0',
        'numpy>=1.20.0',
        'pandas>=1.2.0'
    ],
    
    # Optional dependencies
    extras_require={
        'dev': [
            'pytest>=6.0',
            'black>=21.0',
            'flake8>=3.9'
        ],
        'docs': [
            'sphinx>=4.0',
            'sphinx-rtd-theme>=1.0'
        ]
    },
    
    # Python version requirement
    python_requires='>=3.7',
    
    # Classifiers
    classifiers=[
        'Development Status :: 4 - Beta',
        'Intended Audience :: Developers',
        'Topic :: Software Development :: Libraries :: Python Modules',
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: 3.8',
        'Programming Language :: Python :: 3.9',
        'Programming Language :: Python :: 3.10',
        'Programming Language :: Python :: 3.11',
    ],
    
    # Keywords for PyPI search
    keywords='data processing, utilities, example',
    
    # License
    license='MIT',
    
    # Entry points for command-line scripts
    entry_points={
        'console_scripts': [
            'mypackage=mypackage.cli:main',
        ],
    },
    
    # Include additional files
    include_package_data=True,
    
    # Project URLs
    project_urls={
        'Bug Reports': 'https://github.com/username/mypackage/issues',
        'Documentation': 'https://mypackage.readthedocs.io',
        'Source': 'https://github.com/username/mypackage',
    },
)

# === pyproject.toml (Modern approach) ===

"""
# pyproject.toml - Modern package configuration

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "mypackage"
version = "1.0.0"
description = "A short description of your package"
readme = "README.md"
authors = [
    {name = "Your Name", email = "[email protected]"}
]
license = {text = "MIT"}
requires-python = ">=3.7"
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.7",
    "Programming Language :: Python :: 3.8",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
]
keywords = ["data", "processing", "utilities"]
dependencies = [
    "requests>=2.25.0",
    "numpy>=1.20.0",
    "pandas>=1.2.0"
]

[project.optional-dependencies]
dev = [
    "pytest>=6.0",
    "black>=21.0",
    "flake8>=3.9"
]
docs = [
    "sphinx>=4.0",
    "sphinx-rtd-theme>=1.0"
]

[project.urls]
Homepage = "https://github.com/username/mypackage"
Documentation = "https://mypackage.readthedocs.io"
Repository = "https://github.com/username/mypackage"
"Bug Tracker" = "https://github.com/username/mypackage/issues"

[project.scripts]
mypackage = "mypackage.cli:main"

[tool.setuptools.packages.find]
where = ["."]
exclude = ["tests*", "docs*"]

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"

[tool.black]
line-length = 88
target-version = ['py37', 'py38', 'py39', 'py310', 'py311']
"""

# === setup.cfg (Static configuration) ===

"""
# setup.cfg - Static configuration alternative

[metadata]
name = mypackage
version = attr: mypackage.__version__
author = Your Name
author_email = [email protected]
description = A short description of your package
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/username/mypackage
license = MIT
classifiers =
    Development Status :: 4 - Beta
    Intended Audience :: Developers
    License :: OSI Approved :: MIT License
    Programming Language :: Python :: 3
    Programming Language :: Python :: 3.7
    Programming Language :: Python :: 3.8
    Programming Language :: Python :: 3.9
keywords = data, processing, utilities

[options]
packages = find:
python_requires = >=3.7
install_requires =
    requests>=2.25.0
    numpy>=1.20.0
    pandas>=1.2.0

[options.packages.find]
exclude =
    tests*
    docs*

[options.extras_require]
dev =
    pytest>=6.0
    black>=21.0
    flake8>=3.9

[options.entry_points]
console_scripts =
    mypackage = mypackage.cli:main
"""

# === MANIFEST.in (Include additional files) ===

"""
# MANIFEST.in - Specify additional files to include

include README.md
include LICENSE
include CHANGELOG.md
include requirements.txt
recursive-include mypackage *.json *.yaml
recursive-include docs *.md *.rst
recursive-exclude * __pycache__
recursive-exclude * *.py[co]
"""

# === requirements.txt (Development dependencies) ===

"""
# requirements.txt - Development dependencies

# Testing
pytest>=6.0
pytest-cov>=2.12

# Code quality
black>=21.0
flake8>=3.9
mypy>=0.910

# Documentation
sphinx>=4.0
sphinx-rtd-theme>=1.0

# Build tools
build>=0.7
twine>=3.4
"""

# === README.md (Project documentation) ===

"""
# MyPackage

A comprehensive Python package for data processing and utilities.

## Installation

```bash
pip install mypackage
```

## Quick Start

```python
from mypackage import MainClass, main_function

# Create instance
obj = MainClass()
result = obj.process("data")

# Use function
output = main_function("arg1", "arg2")
```

## Features

- Feature 1: Data processing
- Feature 2: Utility functions
- Feature 3: Easy integration

## Documentation

Full documentation available at: https://mypackage.readthedocs.io

## Contributing

Contributions welcome! Please read CONTRIBUTING.md first.

## License

MIT License - see LICENSE file for details.
"""

print("Configuration examples completed!")
Use pyproject.toml for New Projects: Modern Python packaging prefers pyproject.toml following PEP 517/518. More standardized than setup.py.

Building and Publishing Packages

Building packages creates distribution archives including source distributions and wheels. Publishing uploads distributions to PyPI making packages installable via pip. The process involves building with build module or setup.py, uploading with twine for secure authentication, and managing versions following semantic versioning. Understanding build and publish workflows enables professional package distribution.

pythonbuilding_publishing.py
# Building and Publishing Packages

"""
Building and Publishing Workflow
"""

# === Step 1: Prepare package ===

# Install build tools
# pip install build twine

# === Step 2: Build distributions ===

# Modern approach using build module
# python -m build

# Creates:
# - dist/mypackage-1.0.0.tar.gz (source distribution)
# - dist/mypackage-1.0.0-py3-none-any.whl (wheel)

# Legacy approach using setup.py
# python setup.py sdist bdist_wheel

# === Step 3: Check distribution ===

# Verify distribution with twine
# twine check dist/*

# === Step 4: Upload to Test PyPI (testing) ===

# Register on test.pypi.org first
# twine upload --repository testpypi dist/*

# Test installation from Test PyPI
# pip install --index-url https://test.pypi.org/simple/ mypackage

# === Step 5: Upload to PyPI (production) ===

# Register on pypi.org
# twine upload dist/*

# === Version management ===

"""
Semantic Versioning: MAJOR.MINOR.PATCH

- MAJOR: Incompatible API changes
- MINOR: Backward-compatible functionality additions
- PATCH: Backward-compatible bug fixes

Examples:
- 1.0.0: Initial release
- 1.0.1: Bug fix
- 1.1.0: New feature
- 2.0.0: Breaking changes
"""

# Update version in __init__.py
# __version__ = "1.1.0"

# === .pypirc configuration ===

"""
# ~/.pypirc - PyPI credentials configuration

[distutils]
index-servers =
    pypi
    testpypi

[pypi]
username = __token__
password = pypi-YOUR-API-TOKEN

[testpypi]
repository = https://test.pypi.org/legacy/
username = __token__
password = pypi-YOUR-TEST-API-TOKEN
"""

# === Build script ===

import subprocess
import shutil
import os

def clean_build_artifacts():
    """Remove old build artifacts."""
    dirs_to_remove = ['build', 'dist', '*.egg-info']
    
    for pattern in dirs_to_remove:
        for item in os.listdir('.'):
            if pattern.replace('*', '') in item:
                if os.path.isdir(item):
                    shutil.rmtree(item)
                    print(f"Removed {item}")

def build_package():
    """Build package distributions."""
    print("Building package...")
    
    # Clean old builds
    clean_build_artifacts()
    
    # Build using build module
    result = subprocess.run(
        ['python', '-m', 'build'],
        capture_output=True,
        text=True
    )
    
    if result.returncode == 0:
        print("Build successful!")
        print(result.stdout)
    else:
        print("Build failed!")
        print(result.stderr)
        return False
    
    return True

def check_distributions():
    """Check distribution files."""
    print("\nChecking distributions...")
    
    result = subprocess.run(
        ['twine', 'check', 'dist/*'],
        capture_output=True,
        text=True,
        shell=True
    )
    
    print(result.stdout)
    
    if result.returncode != 0:
        print("Distribution check failed!")
        print(result.stderr)
        return False
    
    return True

def upload_to_testpypi():
    """Upload to Test PyPI."""
    print("\nUploading to Test PyPI...")
    
    result = subprocess.run(
        ['twine', 'upload', '--repository', 'testpypi', 'dist/*'],
        capture_output=True,
        text=True
    )
    
    if result.returncode == 0:
        print("Upload to Test PyPI successful!")
        print(result.stdout)
    else:
        print("Upload failed!")
        print(result.stderr)

def upload_to_pypi():
    """Upload to PyPI."""
    print("\nUploading to PyPI...")
    
    # Confirmation
    confirm = input("Upload to production PyPI? (yes/no): ")
    
    if confirm.lower() != 'yes':
        print("Upload cancelled")
        return
    
    result = subprocess.run(
        ['twine', 'upload', 'dist/*'],
        capture_output=True,
        text=True
    )
    
    if result.returncode == 0:
        print("Upload to PyPI successful!")
        print(result.stdout)
    else:
        print("Upload failed!")
        print(result.stderr)

def main():
    """Main build and publish workflow."""
    print("=== Package Build and Publish Workflow ===")
    
    # Build
    if not build_package():
        return
    
    # Check
    if not check_distributions():
        return
    
    # Choose upload destination
    print("\nUpload options:")
    print("1. Test PyPI (testing)")
    print("2. PyPI (production)")
    print("3. Skip upload")
    
    choice = input("Choose option (1/2/3): ")
    
    if choice == '1':
        upload_to_testpypi()
    elif choice == '2':
        upload_to_pypi()
    else:
        print("Skipping upload")

# === Release checklist script ===

def release_checklist():
    """Pre-release checklist."""
    print("=== Release Checklist ===")
    
    checks = [
        "Updated version number in __init__.py",
        "Updated CHANGELOG.md with changes",
        "All tests passing (pytest)",
        "Code formatted (black)",
        "Linting passed (flake8)",
        "Documentation updated",
        "README.md reviewed",
        "License file included",
        "Git tag created for version",
        "Build artifacts cleaned"
    ]
    
    print("\nPlease confirm the following:")
    for i, check in enumerate(checks, 1):
        print(f"{i}. {check}")
    
    confirm = input("\nAll items completed? (yes/no): ")
    return confirm.lower() == 'yes'

# === Version bumping ===

import re

def bump_version(version_type='patch'):
    """Bump version number."""
    
    # Read current version
    with open('mypackage/__init__.py', 'r') as f:
        content = f.read()
    
    # Extract current version
    match = re.search(r'__version__\s*=\s*["\']([^"\']*)["\'']', content)
    if not match:
        print("Version not found!")
        return
    
    current = match.group(1)
    major, minor, patch = map(int, current.split('.'))
    
    # Bump version
    if version_type == 'major':
        major += 1
        minor = 0
        patch = 0
    elif version_type == 'minor':
        minor += 1
        patch = 0
    else:  # patch
        patch += 1
    
    new_version = f"{major}.{minor}.{patch}"
    
    # Update version
    new_content = re.sub(
        r'__version__\s*=\s*["\'][^"\']*["\']',
        f'__version__ = "{new_version}"',
        content
    )
    
    with open('mypackage/__init__.py', 'w') as f:
        f.write(new_content)
    
    print(f"Version bumped: {current} -> {new_version}")

# Example usage (commented)
# if __name__ == '__main__':
#     if release_checklist():
#         main()
#     else:
#         print("Please complete all checklist items first")

print("Build and publish examples completed!")
Test on Test PyPI First: Always upload to test.pypi.org before production. Test installation and functionality. Production uploads cannot be deleted.

Package Development Best Practices

  • Follow semantic versioning: Use MAJOR.MINOR.PATCH format clearly indicating compatibility. Major for breaking changes, minor for features, patch for fixes
  • Write comprehensive documentation: Include clear README with installation, quick start, and examples. Use docstrings for all public APIs. Maintain API documentation
  • Include tests: Write unit tests with pytest or unittest. Include tests in source distribution but exclude from wheel. Aim for high coverage
  • Specify dependencies carefully: Use minimum version constraints (>=) not exact (==). Specify python_requires for Python version compatibility. Document optional dependencies
  • Use proper licensing: Include LICENSE file clearly stating terms. Add license classifier to setup configuration. Popular choices: MIT, Apache, GPL
  • Maintain CHANGELOG: Document all changes, additions, and fixes per version. Helps users understand updates and migration requirements
  • Add classifiers: Use PyPI classifiers categorizing development status, audience, topics, and Python versions. Improves discoverability
  • Test installation locally: Install package in virtual environment before publishing. Test importing and functionality. Verify dependencies install correctly
  • Use API tokens for PyPI: Create API tokens instead of passwords for secure uploads. Scope tokens to specific projects limiting access
  • Automate with CI/CD: Use GitHub Actions, GitLab CI, or similar automating tests, builds, and publishing. Ensures consistency and quality
Never Upload Same Version Twice: PyPI doesn't allow replacing versions. Always bump version for changes. Use Test PyPI for testing.

Conclusion

Creating Python packages transforms code into distributable libraries through proper structure, configuration, and publishing workflows. Package structure organizes code with package directories containing __init__.py marking as packages, modules providing functionality, subpackages for organization, tests directory with unit tests using pytest or unittest, docs for documentation, examples demonstrating usage, and configuration files at project root. Configuration uses setup.py with setuptools defining name, version, author, description reading from README, install_requires specifying dependencies, classifiers categorizing package, entry_points creating command-line scripts, and find_packages() discovering package structure, or modern pyproject.toml following PEP 517/518 with [build-system] specifying build backend, [project] defining metadata, dependencies, and classifiers, and [tool] sections for development tool configuration.

Building distributions uses build module with python -m build creating source distributions as tar.gz archives and wheel files as .whl for faster installation, or legacy setup.py with sdist for source and bdist_wheel for wheels. Publishing to PyPI involves registering accounts on pypi.org for production and test.pypi.org for testing, using twine for secure uploads with check verifying distributions, upload --repository testpypi testing before production, and upload releasing to production PyPI, configuring .pypirc storing API tokens preferably project-scoped for security. Version management follows semantic versioning with MAJOR.MINOR.PATCH format where major indicates breaking changes, minor adds backward-compatible features, patch fixes bugs, updating __version__ in package __init__.py, maintaining CHANGELOG documenting changes, and creating git tags marking releases. Best practices emphasize following semantic versioning clearly communicating compatibility, writing comprehensive documentation including README, docstrings, and API documentation, including tests ensuring quality with good coverage, specifying dependencies carefully using minimum version constraints, using proper licensing with LICENSE file and classifiers, maintaining CHANGELOG documenting all changes, adding classifiers improving discoverability, testing installation locally in clean environments, using API tokens for secure authentication, and automating with CI/CD pipelines. By mastering package structure with proper organization, configuration files defining metadata and dependencies, building distributions creating source and wheel archives, publishing workflows uploading to PyPI securely, version management following semantic versioning, and best practices ensuring quality and maintainability, you gain essential skills for professional package distribution enabling code sharing, establishing open-source contributions, building reusable libraries, providing versioned releases, managing dependencies properly, and participating in Python ecosystem supporting software development workflows and community collaboration.

$ cat /comments/ (0)

new_comment.sh

// Email hidden from public

>_

$ cat /comments/

// No comments found. Be the first!

[session] guest@{codershandbook}[timestamp] 2026

Navigation

Categories

Connect

Subscribe

// 2026 {Coders Handbook}. EOF.