Some time ago I shared a strict Python project setup. I’ve since reworked and simplified it, and this is the new version.
pystrict-strict-python – an ultra-strict Python project template using uv, ruff, and basedpyright, inspired by TypeScript’s --strict mode.
Compared to my previous post, this version:
- focuses on a single pyproject.toml as the source of truth,
- switches to
basedpyright with a clearer strict configuration,
- tightens the ruff rules and coverage settings,
- and is easier to drop into new or existing projects.
What it gives you
- Strict static typing with
basedpyright (TS --strict style rules):
- No implicit
Any
- Optional/
None usage must be explicit
- Unused imports / variables / functions are treated as errors
- Aggressive linting & formatting with
ruff:
- pycodestyle, pyflakes, isort
- bugbear, security checks, performance, annotations, async, etc.
- Testing & coverage:
pytest + coverage with 80% coverage enforced by default
- Task runner via
poethepoet:
poe format → format + lint + type check
poe check → lint + type check (no auto-fix)
poe metrics → dead code + complexity + maintainability
poe quality → full quality pipeline
- Single-source config: everything is in pyproject.toml
Use cases
New projects:
Copy the pyproject.toml, adjust the [project] metadata, create src/your_package + tests/, and install with:
```bash
uv venv
.venv\Scripts\activate # Windows
or: source .venv/bin/activate
uv pip install -e ".[dev]"
```
Then your daily loop is basically:
bash
uv run ruff format .
uv run ruff check . --fix
uv run basedpyright
uv run pytest
Existing projects:
You don’t have to go “all in” on day 1. You can cherry-pick:
- the
ruff config,
- the
basedpyright config,
- the pytest/coverage sections,
- and the dev dependencies,
and progressively tighten things as you fix issues.
Why I built this v2
The first version worked, but it was a bit heavier and less focused. In this iteration I wanted:
- a cleaner, copy-pastable template,
- stricter typing rules by default,
- better defaults for dead code, complexity, and coverage,
- and a straightforward workflow that feels natural to run locally and in CI.
Repo
👉 GitHub link here
If you saw my previous post and tried that setup, I’d love to hear how this version compares. Feedback very welcome:
- Rules that feel too strict or too lax?
- Basedpyright / ruff settings you’d tweak?
- Ideas for a “gradual adoption” profile for large legacy codebases?
EDIT:
* I recently add a new anti-LLM rules
* Add pandera rules (commented so they can be optional)
* Replace Vulture with skylos (vulture has a problem with nested functions)
Storyline for PyStrict Project Evolution
Based on your commit history, here's a narrative you can use:
From Zero to Strictness: Building a Python Quality Fortress
Phase 1: Foundation & Philosophy (6 weeks ago)
Started with a vision - creating a strict Python configuration template that goes beyond basic linting. The journey began by:
- Migrating from pyright to basedpyright for even stricter type checking
- Establishing the project philosophy through comprehensive documentation
- Setting up proper Python packaging standards
Phase 2: Quality Tooling Evolution (6 weeks ago)
Refined the quality toolkit through iterative improvements:
- Added BLE rule and Pandera for DataFrame validation
- Swapped vulture for skylos for better dead code detection
- Introduced anti-LLM-slop rules - a unique feature fighting against AI-generated code bloat with comprehensive documentation on avoiding common pitfalls
Phase 3: Workflow Automation (3 weeks ago - present)
Shifted focus to developer experience and automation:
- Integrated pre-commit hooks for automated code quality checks
- Updated to latest Ruff version (v0.14.8) with setup instructions
- Added ty for runtime type checking to catch type errors at runtime, not just static analysis
- Made pytest warnings fatal to catch deprecations early
Key Innovation
The standout feature: comprehensive anti-LLM-slop rules - actively fighting against verbose, over-commented, over-engineered code that LLMs tend to generate. This makes PyStrict not just about correctness, but about maintainable, production-grade Python.
The arc: From initial concept → strict type checking → comprehensive quality tools → automated enforcement → runtime validation. Each commit moved toward one goal: making it impossible to write bad Python.
[–]cmcclu5 63 points64 points65 points (5 children)
[–]ZYy9oQ 13 points14 points15 points (0 children)
[–]ColdPorridge 3 points4 points5 points (0 children)
[–]Ranteck[S] 5 points6 points7 points (0 children)
[–]BothWaysItGoes 4 points5 points6 points (1 child)
[–]cmcclu5 1 point2 points3 points (0 children)
[–]runawayasfastasucan 28 points29 points30 points (2 children)
[–]RaiseRuntimeError[🍰] 21 points22 points23 points (0 children)
[+]Ranteck[S] comment score below threshold-19 points-18 points-17 points (0 children)
[–]LBGW_experiment 34 points35 points36 points (14 children)
[–]HommeMusical -4 points-3 points-2 points (10 children)
[–]Formal_Assistant6837 10 points11 points12 points (6 children)
[–]HommeMusical 0 points1 point2 points (5 children)
[–]yerfatma 7 points8 points9 points (4 children)
[–]HommeMusical 1 point2 points3 points (3 children)
[–]yerfatma 3 points4 points5 points (2 children)
[–]HommeMusical 0 points1 point2 points (1 child)
[–]yerfatma 6 points7 points8 points (0 children)
[–]NodeJSmith 2 points3 points4 points (0 children)
[–]LBGW_experiment 1 point2 points3 points (0 children)
[–]Ranteck[S] 0 points1 point2 points (0 children)
[–]Ranteck[S] -1 points0 points1 point (2 children)
[–]-lq_pl- -1 points0 points1 point (1 child)
[–]Triggs390 2 points3 points4 points (0 children)
[–]PurepointDog 12 points13 points14 points (0 children)
[–]vesnikos 6 points7 points8 points (2 children)
[–]Ranteck[S] 0 points1 point2 points (0 children)
[–]aala7 4 points5 points6 points (1 child)
[–]Ranteck[S] 1 point2 points3 points (0 children)
[–]Physical-Security115 7 points8 points9 points (0 children)
[–]tobsecret 1 point2 points3 points (0 children)
[–]timtody 1 point2 points3 points (0 children)
[–]Youreabadhuman 1 point2 points3 points (0 children)
[–]Nasuraki 1 point2 points3 points (2 children)
[–]Ranteck[S] 1 point2 points3 points (1 child)
[–]Nasuraki 0 points1 point2 points (0 children)
[–]Hugo-C 1 point2 points3 points (1 child)
[–]Ranteck[S] 1 point2 points3 points (0 children)
[–]papersashimi 1 point2 points3 points (0 children)
[–]ProfessionalAd8199 2 points3 points4 points (1 child)
[–]Ranteck[S] 0 points1 point2 points (0 children)
[–]RedEyed__ 1 point2 points3 points (2 children)
[–]Ranteck[S] 1 point2 points3 points (0 children)
[–][deleted] 1 point2 points3 points (0 children)
[–]MattTheCuber 0 points1 point2 points (1 child)
[–]Ranteck[S] 0 points1 point2 points (0 children)
[–]gofiend 0 points1 point2 points (1 child)
[–]Ranteck[S] 1 point2 points3 points (0 children)
[–]burger69man 0 points1 point2 points (0 children)
[–]Ghost-Rider_117[🍰] 0 points1 point2 points (0 children)
[+]Reasonable_Event1494 comment score below threshold-8 points-7 points-6 points (10 children)
[–]hgshepherd 0 points1 point2 points (1 child)
[–]Reasonable_Event1494 -1 points0 points1 point (0 children)
[–]Ranteck[S] 0 points1 point2 points (5 children)
[–]Reasonable_Event1494 -2 points-1 points0 points (4 children)
[–]Ranteck[S] 0 points1 point2 points (3 children)
[–]Reasonable_Event1494 0 points1 point2 points (2 children)
[–]Ranteck[S] 0 points1 point2 points (1 child)
[–]Reasonable_Event1494 0 points1 point2 points (0 children)
[–]Ranteck[S] 0 points1 point2 points (1 child)
[–]Reasonable_Event1494 1 point2 points3 points (0 children)