How Modern Python Tooling Simplified My Setup
Working on something tricky in Python? I coach developers 1:1. How it works →
A few years ago, setting up a Python project meant wiring together six or seven tools before writing a single line of code. Today that list has shrunk dramatically. The tools are faster, more ergonomic, and far simpler to configure.
The Old Python Tooling Setup
A "proper" Python project circa 2021 looked something like this:
pyenv install 3.10.4 # manage Python versions
python -m venv .venv # create virtualenv
pip install -e ".[dev]" # install deps (slow)
black . # format
isort . # sort imports (separate tool!)
flake8 . # lint
mypy . # type check
Seven commands, seven config sections. Each tool had its own config format. Sometimes tools needed a compatibility setting so they wouldn't step on each other's toes (for example black and isort). flake8 didn't support pyproject.toml without a plugin. Pre-commit required much config just to wire these together.
And releasing to PyPI? Even after pyproject.toml simplified packaging, you still need to choose a build backend like Hatchling, Flit, or Setuptools, and then, unless your tool has a built-in uploader, use Twine to publish.
uv, ruff, and ty: The Modern Stack
Nowadays we can just do:
uv init myproject && cd myproject
uv add fastapi sqlmodel
uv add --dev ruff ty pytest
uv run ruff check
uv run ruff format
uv run ty check
uv run pytest
uv handles Python versions, virtualenvs, dependency resolution, locking, and building. If you've written Rust, this will feel familiar: uv brings the cargo experience to Python. ruff replaces black, isort, and flake8 in a single binary. ty is the new type checker from the same Astral team and is much faster than mypy.
Here's a real pyproject.toml from one of my projects:
[project]
name = "mdweaver"
version = "0.2.0"
requires-python = ">=3.12"
dependencies = [
"markdown>=3.5",
"pygments>=2.17",
"weasyprint>=60.0",
]
[dependency-groups]
dev = ["pytest>=8.0", "pytest-cov>=4.0", "ruff>=0.9.0", "tbump>=6.11.0", "ty>=0.0.12"]
[build-system]
requires = ["uv_build>=0.10.8,<0.11.0"]
build-backend = "uv_build"
No [tool.black], no [tool.isort], no [tool.mypy]. Ruff works with sensible defaults. One file configures everything. And notice the build backend: uv_build means even packaging is handled by uv — no separate hatchling or setuptools dependency needed.
Pre-commit Shrinks Too
The pre-commit config across my projects went from 30+ lines (black, isort, flake8, mypy hooks) to this:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.4
hooks:
- id: ruff
args: [--fix, --extend-select=I]
- id: ruff-format
One tool does what three used to.
And if you want the hook runner itself to be fast too, prek is a Rust-based drop-in replacement for pre-commit. Same .pre-commit-config.yaml config, faster execution.
Releasing Got Simple Too
Shipping a package to PyPI used to mean a build backend, twine upload, and API token management. Now I use tbump (ok this is a separate tool) and trusted publishing.
$ uv run tbump 0.2.0
:: Bumping from 0.1.0 to 0.2.0
=> Would patch these files
- pyproject.toml:3 version = "0.1.0"
+ pyproject.toml:3 version = "0.2.0"
=> Would run these git commands
$ git add --update
$ git commit --message chore: bump version to 0.2.0
$ git tag --annotate --message v0.2.0 v0.2.0
$ git push --atomic origin main v0.2.0
:: Looking good? (y/N)
> y
One command bumps the version, commits, tags, and pushes. The tag triggers a GitHub Action that runs lint, tests, uv build, and publishes to PyPI. No API tokens to manage, no twine.
I learned this workflow from Automate Python package releases — worth reading if you publish packages.
A Note on Maturity
Back to the Astral stack. These tools are young. At the time of writing, ty is still in beta, and Astral is being acquired by OpenAI, which is causing mixed feelings in the community. The pace of development is fast, and the tools already work well for most projects. Since switching to uv I've not looked back.
Resources
The Python Developer Tooling Handbook has many useful short guides and tips, a resource to watch.
I spoke with creator Charlie Marsh on the Pybites Podcast about the design decisions behind these tools:
- #175: Charlie Marsh on Ruff, uv and designing fast + ergonomic Python tooling
- #199: Charlie Marsh on ty, uv, and the Python tooling renaissance
If you're still wiring together six tools to start a Python project, try this stack for your next one. The setup time alone will convince you.
Tutorials teach syntax. Courses teach patterns. AI gives unvetted code. None of them review your decisions on your code. That's what 1:1 coaching is for. Here's how it works →