Help us fix this page
If you found a broken link, missing page, or incorrect redirect, please let us know. Your report helps us improve the website for everyone.

Creating a Python module requires more than just writing code. Modern development demands proper tooling, standardized workflows, and automation. In this guide, we’ll walk through setting up a robust Python project, complete with testing, linting, CI/CD pipelines, and PyPI publishing.
pyproject.toml as your primary configuration file-it’s the modern standard (PEP 518/621) for Python project metadata, replacing setup.py for most use cases.src/ layout-place your package code in src/your_package_name/ to enforce clean imports and prevent testing against the source directory.Makefile-define targets for testing, linting, formatting, and building to ensure consistency across your team.pytest-it’s the most popular and flexible testing framework for Python, with built-in discovery and powerful plugins.Before diving into module creation, ensure you have Python installed. As of August 2026, Python 3.13.3 is the latest stable release.
Windows:
python --version in PowerShell/CMDLinux (Debian/Ubuntu):
sudo apt update
sudo apt install python3 python3-pip python3-venvLinux (Fedora/CentOS/RHEL):
sudo dnf install python3 python3-pipmacOS:
brew update
brew install python@3.13Recommendation for Windows developers: Use WSL2 (Windows Subsystem for Linux) for a more seamless Python development experience. Many Python tools are optimized for Unix-like environments.
Install these tools globally or in each virtual environment:
pip install setuptools wheel flake8 pytest black pytest-cov mypy| Tool | Purpose |
|---|---|
| setuptools | Packaging and distribution |
| wheel | Built package format |
| flake8 | Code linting (PEP 8 compliance) |
| pytest | Unit testing framework |
| black | Code formatting (opinionated, automatic) |
| pytest-cov | Test coverage reporting |
| mypy | Static type checking |
A well-defined structure is crucial for maintainability. Here’s the recommended layout:
your_package_name/
├── .github/
│ └── workflows/
│ ├── python-publish.yml # PyPI publishing workflow
│ └── tests.yml # CI test workflow
├── src/
│ └── your_package_name/
│ ├── __init__.py
│ ├── core.py
│ └── utils.py
├── tests/
│ ├── __init__.py
│ └── test_core.py
├── .gitattributes
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── pyproject.toml
├── README.md
└── requirements.txt| Component | Purpose |
|---|---|
.github/workflows/ | GitHub Actions CI/CD pipelines |
src/your_package_name/ | Package source code (the src/ layout is now recommended over placing code at the root) |
tests/ | Unit test files |
pyproject.toml | Modern project metadata and build configuration |
Makefile | Automation for development tasks |
README.md | Project documentation (first point of contact for users) |
CHANGELOG.md | Version history following Keep a Changelog |
LICENSE | Open-source license terms |
src/ Layout?The src/ layout (placing package code in src/your_package_name/) is now the recommended practice:
src/, tests may import the local source instead of the installed packageA Makefile automates repetitive development tasks. It ensures consistency across team members and reduces human error.
.PHONY: test lint format install uninstall coverage clean check
# Default target
help:
@echo "Available commands:"
@echo " make test - Run all tests with pytest"
@echo " make lint - Run flake8 linting"
@echo " make format - Format code with black"
@echo " make coverage - Generate test coverage report"
@echo " make install - Install package in editable mode"
@echo " make uninstall - Uninstall the package"
@echo " make check - Run both lint and test"
@echo " make clean - Remove build artifacts"
test:
pytest tests/ -v
lint:
flake8 src/ tests/
format:
black src/ tests/
coverage:
pytest --cov=your_package_name tests/ --cov-report=term --cov-report=html
@echo "HTML coverage report generated in htmlcov/"
install:
pip install -e .
uninstall:
pip uninstall -y your_package_name
check: lint test
clean:
rm -rf build/ dist/ *.egg-info htmlcov/ .pytest_cache/
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || truemake check # Run linting and tests before committing
make test # Run only tests
make coverage # Generate coverage report
make install # Install package in editable mode (-e)
make clean # Remove build artifactsNote: On Windows without make, you can use pytest and flake8 directly, or install make via Chocolatey: choco install make
pyproject.toml is the modern standard for Python project configuration, defined by PEP 518 (build system requirements) and PEP 621 (project metadata). It replaces setup.py and setup.cfg for most use cases.
[project]
name = "your_package_name"
version = "1.0.0"
description = "A brief description of your package"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
authors = [
{name = "Your Name", email = "your.email@example.com"}
]
maintainers = [
{name = "Your Name", email = "your.email@example.com"}
]
keywords = ["python", "library", "utilities"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Typing :: Typed",
]
dependencies = [
"typing-extensions>=4.0.0; python_version<'3.11'",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-cov>=4.0.0",
"flake8>=7.0.0",
"black>=24.0.0",
"mypy>=1.0.0",
]
[project.urls]
Homepage = "https://github.com/your_username/your_package_name"
Repository = "https://github.com/your_username/your_package_name"
"Bug Tracker" = "https://github.com/your_username/your_package_name/issues"
"Change Log" = "https://github.com/your_username/your_package_name/blob/main/CHANGELOG.md"
[build-system]
build-backend = "setuptools.build_meta"
requires = ["setuptools>=65.0.0", "wheel"]
[tool.black]
line-length = 88
target-version = ['py310', 'py311', 'py312', 'py313']
[tool.pytest.ini_options]
minversion = "8.0"
addopts = "-ra -q"
testpaths = ["tests"]
[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
ignore_missing_imports = true| Section | Purpose |
|---|---|
[project] | Core metadata: name, version, dependencies, license |
[project.optional-dependencies] | Development and extra dependencies (install with pip install .[dev]) |
[project.urls] | Links to project resources (Homepage, Repository, Bug Tracker) |
[build-system] | Specifies the build backend and requirements |
[tool.*] | Configuration for development tools (black, pytest, mypy) |
# Install with development dependencies
pip install -e .[dev]
# Install with specific extras
pip install your_package_name[test,aws]Unit tests verify individual components of your code. pytest is the most widely used testing framework in the Python ecosystem.
tests/test_core.pyimport pytest
from your_package_name.core import add, subtract, divide
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
assert add(0, 0) == 0
def test_subtract():
assert subtract(10, 4) == 6
assert subtract(0, 5) == -5
def test_divide():
assert divide(10, 2) == 5.0
assert divide(5, 2) == 2.5
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError):
divide(10, 0)
# Parameterized tests for multiple cases
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3),
(10, 20, 30),
(-5, 5, 0),
])
def test_add_parametrized(a, b, expected):
assert add(a, b) == expected# Run all tests
make test
# Run with coverage
make coverage
# Run specific test file
pytest tests/test_core.py -v
# Run tests matching a pattern
pytest -k "test_add"The Python Package Index (PyPI) is the official repository for Python packages. Publishing your package makes it available via pip install your_package_name.
# Format and lint your code
make format
make lint
# Run all tests
make test
# Build the package
python -m buildTrusted Publishers is the modern, secure method for publishing to PyPI. It uses OIDC (OpenID Connect) to authenticate without storing tokens as secrets.
your_package_namepython-publish.ymlCreate .github/workflows/python-publish.yml:
name: Publish Python Package
on:
release:
types: [published]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC (Trusted Publishers)
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1v1.0.0)CHANGELOG.mdThe GitHub Actions workflow will automatically build and publish your package to PyPI.
If you prefer using an API token:
PYPI_API_TOKEN in GitHub Secrets- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}Create .github/workflows/tests.yml to run tests on every push:
name: Run Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .[dev]
- name: Lint
run: flake8 src/ tests/
- name: Test
run: pytest tests/ -v --cov=your_package_name
- name: Upload coverage
uses: codecov/codecov-action@v4| Area | Recommendation |
|---|---|
| Structure | Use src/ layout with pyproject.toml as the primary config |
| Versioning | Follow Semantic Versioning (MAJOR.MINOR.PATCH) |
| Testing | Write tests for edge cases, not just happy paths. Use parametrize for multiple inputs |
| Documentation | Keep README.md and CHANGELOG.md updated with each release |
| Automation | Use make for local tasks, GitHub Actions for CI/CD |
| Publishing | Use Trusted Publishers (OIDC) for security; avoid storing long-lived tokens |
pyproject.toml as your primary configuration file-it’s the modern standard and supports both build configuration and tool settings.src/ layout is recommended-it prevents accidental imports from the source directory and tests what users actually install.Makefile-standardize testing, linting, formatting, and building across your team.pytest-include parametrized tests, edge cases, and coverage reporting.Building a professional Python package involves more than writing code. A well-structured project with automated testing, linting, and CI/CD reduces technical debt, improves collaboration, and makes your package easier for others to use.
Start with a clean project structure using the src/ layout and pyproject.toml. Add a Makefile to automate common tasks. Write thorough tests with pytest. Finally, set up GitHub Actions to test every commit and publish to PyPI on every release.
These practices will serve you whether you’re building a utility library for internal use or a widely used open-source project.
Need help with your Python development? Playful Sparkle has been engineering digital products since 2004, offering App Development, Web Development, and UI/UX & Web Design services. Contact us to discuss how we can help with your next project.