A pyproject.toml boilerplate
•
Pythonpyproject.toml
Pyproject.toml boilerplate
Here is an example of boilerplate I might use for a new project.
# Boilerplate pyproject.toml — replace "my-project" and "my_project" with your names.
# See OPERATIONALIZATION.md for how to use this with uv and your workflow.
#
# For a single-module app (e.g. one cli.py at the root), replace the hatch
# packages line with a py-modules or only-include setup; or use setuptools.
[build-system]
requires = ["hatchling"] # Build dependency: the hatchling build backend
build-backend = "hatchling.build" # Use hatchling to build sdists and wheels
[project]
name = "my-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12" # Minimum Python version required
dependencies = [] # Runtime dependencies (add packages your code imports)
[dependency-groups]
dev = [ # Development tools (linting, formatting, type checking)
"ruff>=0.15.7", # Linter and formatter
"ty>=0.0.24", # Type checker
"rumdl>=0.1.57", # Markdown linter
]
test = [ # Testing tools
"pytest>=9.0.2", # Test runner
"pytest-cov>=7.0.0", # Coverage reporting plugin for pytest
]
docs = [ # Documentation tools
"zensical", # Documentation generator
"mkdocstrings[python]", # Auto-generate API docs from Python docstrings
]
[tool.uv]
default-groups = ["dev", "test", "docs"] # Install all dependency groups by default with uv sync
# Hatch wheel build: which packages or modules to include in the built .whl.
#
# — src layout (default): package lives in src/my_project/
# packages = ["src/my_project"]
#
# — Package at project root (my_project/ next to pyproject.toml):
# packages = ["my_project"]
#
# — Single or multiple .py modules at project root:
# only-include = ["cli.py"]
# only-include = ["mod1.py", "mod2.py"]
#
[tool.hatch.build.targets.wheel]
packages = ["src/my_project"] # Directory to include when building the wheel (src layout)
# For a CLI app, add (adjust module path and callable):
# [project.scripts]
# my-command = "my_project.cli:main"
# ---------------------------------------------------------------------------
# ruff — formatting and linting
# ---------------------------------------------------------------------------
[tool.ruff]
line-length = 100 # Max line length for both linting and formatting
target-version = "py312" # Assume Python 3.12 syntax for linting rules
[tool.ruff.format]
quote-style = "double" # Use double quotes for strings
indent-style = "space" # Use spaces, not tabs, for indentation
[tool.ruff.lint]
select = [
"E", # pycodestyle errors (e.g. whitespace, indentation)
"F", # Pyflakes (unused imports, undefined names)
"I", # isort (import ordering)
"N", # pep8-naming (naming conventions)
"W", # pycodestyle warnings
"UP", # pyupgrade (modernize syntax for target Python version)
"B", # flake8-bugbear (common bug patterns and design problems)
"C4", # flake8-comprehensions (simplify list/dict/set comprehensions)
"SIM", # flake8-simplify (suggest simpler constructs)
]
ignore = [
"E501", # Line too long — handled by the formatter instead
]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"] # Allow unused imports in __init__.py (re-exports)
# ---------------------------------------------------------------------------
# ty — type checking
# ---------------------------------------------------------------------------
[tool.ty.environment]
python-version = "3.12" # Python version to use for type resolution
[tool.ty.src]
exclude = ["tests"] # Skip type checking the tests directory
[tool.ty.rules]
# Promote warnings to errors as the codebase matures:
# possibly-unbound = "error" # Flag variables that may be used before assignment
# invalid-return-type = "error" # Flag functions returning wrong types
[tool.ty.terminal]
output-format = "full"
# ---------------------------------------------------------------------------
# rumdl — Markdown linting
# ---------------------------------------------------------------------------
[tool.rumdl]
flavor = "mkdocs" # Parse mkdocs-specific Markdown extensions (admonitions, tabs, etc.)
line-length = 100 # Max line length for Markdown files
respect-gitignore = true # Also skip files listed in .gitignore
disable = [ # Rules to disable globally
"MD007", # Unordered list indentation — too strict for nested lists
"MD013", # Line length — handled at project level or not enforced
"MD031", # Fenced code blocks surrounded by blank lines
"MD033", # Inline HTML — often needed for docs
"MD046", # Code block style consistency
]
exclude = [".git", ".github", "node_modules", "vendor", "dist", "build", "CHANGELOG.md", "LICENSE.md"] # Directories/files to skip
[tool.rumdl.MD003]
style = "atx" # Heading style: use # headings (not underline/setext)
[tool.rumdl.MD004]
style = "dash" # Unordered list marker: use dashes (- item)
[tool.rumdl.MD013]
line-length = 100 # Line length limit for prose in Markdown
code-blocks = false # Don't enforce line length inside code blocks
tables = false # Don't enforce line length inside tables
[tool.rumdl.MD024]
siblings-only = true # Allow duplicate headings if they're under different parents
[tool.rumdl.MD033]
allowed-elements = ["br", "details", "summary", "img"] # Inline HTML elements that are OK to use
[tool.rumdl.MD044]
names = ["GitHub", "Python", "Markdown"] # Proper nouns that must use correct capitalization
code-blocks = false # Don't enforce capitalization inside code blocks
# ---------------------------------------------------------------------------
# pytest
# ---------------------------------------------------------------------------
[tool.pytest.ini_options]
testpaths = ["tests"] # Directory where pytest looks for test files
addopts = [
"-v", # Verbose output: show individual test names
"--tb=short", # Use short tracebacks on failure
"--strict-markers", # Fail on unknown @pytest.mark decorators
"--strict-config", # Fail on unrecognized config options
]