JBON_DATA

Python Package Development Best Practices

Well-structured packages make code reusable, testable, and shareable. Whether for internal tooling or open source, these practices ensure your packages are maintainable.

Modern Project Structure

my-package/
├── src/
│   └── my_package/
│       ├── __init__.py
│       ├── core.py
│       └── utils.py
├── tests/
│   ├── __init__.py
│   ├── test_core.py
│   └── conftest.py
├── docs/
├── pyproject.toml
├── README.md
└── LICENSE

pyproject.toml Configuration

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "my-package"
version = "0.1.0"
description = "A useful package"
readme = "README.md"
requires-python = ">=3.9"
license = "MIT"
authors = [
    { name = "Your Name", email = "you@example.com" }
]
dependencies = [
    "pandas>=2.0",
    "requests>=2.28"
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "pytest-cov",
    "ruff",
    "mypy"
]

[project.scripts]
my-tool = "my_package.cli:main"

[tool.ruff]
target-version = "py39"
line-length = 88

[tool.pytest.ini_options]
testpaths = ["tests"]

[tool.mypy]
python_version = "3.9"
strict = true

Package Initialization

# src/my_package/__init__.py
"""My Package - A useful package for data operations."""

from my_package.core import DataProcessor, transform
from my_package.utils import validate, format_output

__version__ = "0.1.0"

__all__ = [
    "DataProcessor",
    "transform",
    "validate",
    "format_output",
]

Type Hints

from typing import Optional, Union
from pathlib import Path
import pandas as pd

def process_file(
    filepath: Union[str, Path],
    output_format: str = "csv",
    columns: Optional[list[str]] = None
) -> pd.DataFrame:
    """
    Process a data file.
    
    Args:
        filepath: Path to input file
        output_format: Output format (csv, parquet)
        columns: Columns to include
        
    Returns:
        Processed DataFrame
        
    Raises:
        FileNotFoundError: If file doesn't exist
        ValueError: If output_format not supported
    """
    path = Path(filepath)
    if not path.exists():
        raise FileNotFoundError(f"File not found: {path}")
    
    # Implementation
    ...

Testing

# tests/test_core.py
import pytest
from my_package import DataProcessor

class TestDataProcessor:
    @pytest.fixture
    def processor(self):
        return DataProcessor()
    
    def test_basic_processing(self, processor, sample_data):
        result = processor.process(sample_data)
        assert len(result) > 0
    
    @pytest.mark.parametrize("input,expected", [
        ("valid", True),
        ("", False),
        (None, False),
    ])
    def test_validation(self, processor, input, expected):
        assert processor.validate(input) == expected

Development Workflow

# Create virtual environment
python -m venv .venv
source .venv/bin/activate

# Install in editable mode with dev dependencies
pip install -e ".[dev]"

# Run tests
pytest --cov=my_package

# Type checking
mypy src/

# Linting
ruff check src/

# Build package
python -m build

# Publish to PyPI
twine upload dist/*

Invest in package structure early—it pays off as your codebase grows.

← Back to Blog