End-to-end Python project setup cheatsheet for 2026.
Layout
myproject/
├── pyproject.toml
├── uv.lock
├── README.md
├── .python-version
├── .pre-commit-config.yaml
├── .github/workflows/ci.yml
├── src/myproject/
│ ├── __init__.py
│ └── ...
└── tests/
├── conftest.py
└── test_*.py
Prefer src/ layout — avoids “import works in dev but breaks installed.”
Bootstrap
uv init myproject
cd myproject
uv add fastapi pydantic
uv add --dev pytest pytest-anyio httpx ruff mypy pre-commit
pyproject.toml (complete)
[project]
name = "myproject"
version = "0.1.0"
description = "A description"
authors = [{name = "Me"}]
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"fastapi>=0.115",
"pydantic>=2.9",
]
[dependency-groups]
dev = [
"pytest>=8",
"pytest-anyio>=4",
"httpx>=0.27",
"pytest-cov>=5",
"ruff>=0.7",
"mypy>=1.13",
"pre-commit>=4",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/myproject"]
[tool.ruff]
target-version = "py313"
line-length = 100
[tool.ruff.lint]
select = [
"E", "F", "W", # pycodestyle, pyflakes
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
"B", # bugbear
"C4", # comprehensions
"SIM", # simplify
"ARG", # unused arguments
"RUF", # ruff-specific
"PTH", # use pathlib
"TID", # tidy imports
]
ignore = ["E501"] # let formatter handle line length
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S101"] # allow asserts in tests
[tool.ruff.format]
quote-style = "double"
[tool.mypy]
python_version = "3.13"
strict = true
plugins = ["pydantic.mypy"]
[[tool.mypy.overrides]]
module = ["tests.*"]
disallow_untyped_defs = false
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra --strict-markers"
markers = ["slow: long-running tests"]
filterwarnings = ["error"]
[tool.coverage.run]
source = ["src"]
branch = true
[tool.coverage.report]
fail_under = 80
exclude_lines = [
"pragma: no cover",
"raise NotImplementedError",
"if TYPE_CHECKING:",
]
ruff (format + lint)
uv run ruff format . # format like black
uv run ruff check . # lint
uv run ruff check . --fix # auto-fix
uv run ruff check . --fix --unsafe-fixes
mypy
uv run mypy src
uv run mypy src --strict
pytest
uv run pytest
uv run pytest -v
uv run pytest --cov
pre-commit
.pre-commit-config.yaml:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.7.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-added-large-files
- repo: local
hooks:
- id: mypy
name: mypy
entry: uv run mypy src
language: system
pass_filenames: false
types: [python]
uv run pre-commit install
uv run pre-commit run --all-files
GitHub Actions
.github/workflows/ci.yml:
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.13"]
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
with:
enable-cache: true
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: uv sync --frozen
- run: uv run ruff check .
- run: uv run ruff format --check .
- run: uv run mypy src
- run: uv run pytest --cov --cov-fail-under=80
Dockerfile (production)
# syntax=docker/dockerfile:1.7
FROM python:3.13-slim AS builder
WORKDIR /app
RUN pip install uv
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev
FROM python:3.13-slim
WORKDIR /app
COPY --from=builder /app/.venv ./.venv
COPY src ./src
ENV PATH=/app/.venv/bin:$PATH
USER 1000:1000
CMD ["python", "-m", "myproject"]
.gitignore
__pycache__/
*.py[cod]
.venv/
.pytest_cache/
.mypy_cache/
.ruff_cache/
.coverage
htmlcov/
dist/
build/
.env
Editor config
.editorconfig:
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
[*.{yaml,yml,toml}]
indent_size = 2
Common conventions
src/layout for libraries.pyproject.tomlas single source of truth.uv.lockcommitted.- Pre-commit run locally + in CI.
- mypy
strict = truefrom start (easier than retrofitting). - Coverage
fail_under = 80(or whatever your team commits to).
Read this next
That’s 20 Python cheatsheets. Next category: TypeScript.
If you want my full Python project starter (everything above wired), it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .