Ultra-strict Python template v2 (uv + ruff + basedpyright)
Posted by Ranteck@reddit | Python | View on Reddit | 63 comments
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, andbasedpyright, inspired by TypeScript’s--strictmode.
Compared to my previous post, this version:
- focuses on a single pyproject.toml as the source of truth,
- switches to
basedpyrightwith 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--strictstyle rules): - No implicit
Any - Optional/
Noneusage 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+coveragewith 80% coverage enforced by default- Task runner via
poethepoet: poe format→ format + lint + type checkpoe check→ lint + type check (no auto-fix)poe metrics→ dead code + complexity + maintainabilitypoe quality→ full quality pipeline- Single-source config: everything is in pyproject.toml
Use cases
- New projects:
Copy the pyproject.toml, adjust the[project]metadata, createsrc/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
ruffconfig, - the
basedpyrightconfig, - 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
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?
MattTheCuber@reddit
Why not
select= ["ALL"]?Ranteck@reddit (OP)
I want to be more generic in every case. You can change it in your project but if I see mostly the same, I can change it
rykari-rin@reddit
You can make it more generic using select all btw. You just update exceptions as you need
qravel@reddit
Hi, u/Ranteck
I’m using this template with:
However,
pytestis not installed as part of thedevextra:This results in:
The README mentions pytest and shows it as part of the expected workflow, so I expected it to be included in
dev.Is this intentional, or should
pytest(and possiblypytest-cov) be part of thedevdependencies?Thanks — great template otherwise.
ProfessionalAd8199@reddit
twine,bumpver)?I can understand yours is only focussing on code quality, but maybe you want to change it to a complete project template.
Ranteck@reddit (OP)
Nice! I can incorporate in the future
gofiend@reddit
Have you considered switching to uv format and uv ty to simplify the stack? Any tradeoffs worth considering?
Ranteck@reddit (OP)
Do you mean ty from astral? Is not ready for production sadly
burger69man@reddit
lol need a dockerfile for this template
Reasonable_Event1494@reddit
Thanks although I didn't not understood it completely like in depth but I got an idea that I can use this file to correct my code and even remove the unused or not necessary variables..... Please correct me or tell me that In what languages I can use it.
Ranteck@reddit (OP)
It's all for python
Reasonable_Event1494@reddit
So, .toml files are only used for python
Ranteck@reddit (OP)
I responded to you in an earlier comment. PD: Some good advice for improving your learning: when you have a question like this, or even a simpler one, check it with the AI or Google. This will help you to learn better.
Reasonable_Event1494@reddit
Ok thanks will make sure not disturb with basic questions.. I asked because I still think humans are better in making me understand things than an AI
Ranteck@reddit (OP)
It's ok, I mean it was good advice for studying or learning by yourself. Relax, you don't bother me. Ask all the times you need to
Reasonable_Event1494@reddit
Thanks for it.. And I am relaxed was just sharing..
Ranteck@reddit (OP)
Not only is TOML a configuration file like YAML, it is also not only for Python. It works well in Python, but also in Rust, Go and Node.js. In short, it's a universal configuration file that Python uses too. It is not tied to Python.
Reasonable_Event1494@reddit
Ok thanks
hgshepherd@reddit
Um... Python?
Reasonable_Event1494@reddit
Only Python?
papersashimi@reddit
nice!! really like this! maybe i can contribute to it when i have the time ;)
cmcclu5@reddit
You should also include forced dataframe types via panderas (works for pandas and polars) if you’re going to make this as painful as possible. Explicit variable schemas. No JSON objects, only pydantic (ironic that my phone tried to autocorrect to pedantic) models and dataclasses.
BothWaysItGoes@reddit
It’s not ironic, pydantic is an intended pun.
cmcclu5@reddit
Very “pydantic” response.
ColdPorridge@reddit
Speaking of, is anyone aware of any dataframe type/schema checking for pyspark?
ZYy9oQ@reddit
Agree on pydantic - my "antislop" rulesets designed to try make llms write slightly less terrible code force them to define a pydantic model and
model_validateimmediately on any IO or untyped thing (e.g. argparse namespaces).Some other ones that OP might want to consider are https://docs.astral.sh/ruff/rules/too-many-nested-blocks/ (preview only) https://docs.astral.sh/ruff/rules/complex-structure/ and some I added specifically to avoid "bad habits" the LLM has https://docs.astral.sh/ruff/rules/blind-except/ https://docs.astral.sh/ruff/rules/import-outside-top-level/
Ranteck@reddit (OP)
Love it, thanks
LBGW_experiment@reddit
Why not make the 5 commands into pre-commit hooks, like https://github.com/a5chin/python-uv does or via .vscode/settings.json settings for auto format/run on save? That's what I ended up doing in my last project
HommeMusical@reddit
That's just a horrible idea.
There are a vast number of reasons I might wish to create a commit that doesn't pass the tests.
For one thing, at the end of the day, I commit and push my work. Of course, I'm working on a branch that no one else sees.
Or sometimes I need to create a commit ID for partial changes simply to move it to another branch.
Sometimes I split my commit into individual files and then glue them together to make two commits.
Another thing is that on my current codebase, running all of those jobs on every commit would take quite a long time.
LBGW_experiment@reddit
You can just do
-nor--no-verifyRanteck@reddit (OP)
right but what i can do for example is to fix the code using ruff always to made a commit, ruff format and ruff --fix
NodeJSmith@reddit
Yeah... But --no-verify exists for this reason. Strong pre-commit hooks that have to be overridden are better than having to remember to run all the checks yourself and forgetting half the time, imo
Formal_Assistant6837@reddit
You can always use
--no-verifythough.HommeMusical@reddit
If
--no-verifyis turned on, what good is this as a pre-commit hook? Surely it won't catch errors.yerfatma@reddit
Huh? The idea is you want to enforce the hooks all the time, but if there is a special case where you absolutely need to break the rules,
-nis two keystrokes away.HommeMusical@reddit
Ach, sorry,
--no-verifyis an argument togit commit, not to the linter! I wrote too fast.(But at least on git 2.51.0, you have to type the full flag, there is no
-n.)yerfatma@reddit
Right, I am saying if these are all set up as hooks, you can skip them if you absolutely need to. And I am on 2.51 and can assure you
-nworks.HommeMusical@reddit
You are right, again. But this time I was betrayed by the man page.
The top of https://git-scm.com/docs/git-commit lists alternative flags for some flags, but doesn't mention
-n; it does appear further down in the page.I thought we could rely on that top SYNOPSIS as being complete. Is this not the case, or this is an issue?
Interestingly enough,
-eappears in the synopsis but not--edit.yerfatma@reddit
Don't get used to it.
Ranteck@reddit (OP)
Is the same, I want to generalize and start with a template. This can be changed for convenience
-lq_pl-@reddit
You expect me to run checkers by hand? What is strict about that? Also enforcing coverage greater 80% is nonsense. You either care about correctness, then you bring it to 100%, or you don't, then it doesn't matter where you set the threshold.
Triggs390@reddit
You can put the checkers in CI to still be strict without running them every commit.
Hugo-C@reddit
Nice, you should add "-W error" to Pytest so it fails on warnings (especially usefull to catch deprecations early). Pytest's doc: https://docs.pytest.org/en/stable/how-to/capture-warnings.html#controlling-warnings.
Ranteck@reddit (OP)
nice, i will add it now
Nasuraki@reddit
What’a the motivation?
I understand the motivation for a strict typed programming language. The use of Option and Result like in Rust do a lot for the code stability, quality, clarity etc.
But i almost feel like the point of python is to… not have that? If i wanted all that i would use something like Rust?
I use Python , Rust and TypeScript. I’m not seeing the point unless you really need something that is only available in Python?
This is a serious question, if anyone could answer with use-cases where they would want/need/use this i’d love to hear
Ranteck@reddit (OP)
Think of it this way: the programming language is the means to achieve your goal. In my case, I develop a lot with AI, so the main problem is that most of the libraries or developers are in Python, which has dynamic typing. This makes it really painful to maintain, even if you develop with vibe coding. What I want to avoid is AI errors, losing records or having dead code. You might ask, "Why don't you use TypeScript in strict mode?" Well, I could, but I'm mostly creating new services in Python, so I'm trying both approaches.
Nasuraki@reddit
Yeah i guess if you’re relying on sklearn, pytorch or something similar it makes sense
Youreabadhuman@reddit
I think in based pyright the "recommended" ruleset is better for this
aala7@reddit
Nice! Thanks for working through configs and giving us a good starting point!
Have you considered:
- PEP 735 (dependency groups) might be more suitable than optional dependencies. The latter is more meant as optional features for end users (like AI capabilities for Marimo or email validation for pydantic). The former is purposed for dev, test and similar.
Ranteck@reddit (OP)
Actually, it's a great idea. It's starting to minimise the problem with coding with LLM, so it could be even better to do a script install or something similar. I'll take your recommendation.
vesnikos@reddit
Task is so much better han poe. the main reason is b/c task has built-in core utilities for Windows like `cat`, `gzip` `mkdir` , `rm` and more, allowing you to define tasks that run in both windows and linux environments! https://taskfile.dev/blog/windows-core-utils
poe on the other hand is not that evolved last time i checked (ages ago - things might have changed but idk)
Ranteck@reddit (OP)
nice, i will check it
RedEyed__@reddit
Big thank you!
- I didn't know about
basedpyright! Now I want to add it into my projects.- Your
pyproject.tomllooks interesting, I mean I can learn something new from it - What is poeth? I usejustto define commands, is this something similar?Ranteck@reddit (OP)
Poe let you combine commands, similar to pre commit.
Chypka@reddit
Is poe the poet. :)
runawayasfastasucan@reddit
uv venv .venv\Scripts\activate # Windows
or: source .venv/bin/activate
uv pip install -e ".[dev]" You can just do
Ranteck@reddit (OP)
Yep the llm get confused but also not everyone knows or use uv
RaiseRuntimeError@reddit
That's probably because LLMs get confused with uv since it's pretty new.
timtody@reddit
Love it!!!
Ghost-Rider_117@reddit
this is really clean, appreciate the focus on pyproject.toml as single source of truth
the 80% coverage default is kinda nice—forces you to think about testing from day one without being too crazy about it. been using uv lately and the speed difference vs pip is wild. definitely gonna steal some of these ruff configs for my projects
Physical-Security115@reddit
In the age of LLM slop, this is extremely useful.
PurepointDog@reddit
Justfile in there would be rad
tobsecret@reddit
I've been working on a similar but opposite version of this and looking through your pyproject.toml has been very helpful! Thank you for providing it!