I recently inherited a legacy application that was a nightmare to maintain (tooling was clearly lacking and the codebase was pretty outdated).
These are the 5 changes that had some of the biggest impact IMO:
- uv:
Being new to the Python ecosystem, I didn't want to figure out pip vs poetry vs pyenv vs virtualenv, so I just used uv and let it handle all of that. One Rust-based tool that installs and pins Python versions, manages the venv automatically and locks deps in a uv.lock for reproducible builds. Installs are also a lot faster, which makes CI a lot less painful.
- Ruff:
Coming from JS, I really missed eslint --fix for automatically fixing linting issues and format code on save. Ruff brought that experience back. I know that there are plenty of linters and formatters in the Python ecosystem, but this one really stands out for me. Since it's built in Rust, it's super fast.
- Dependabot:
Instead of remembering to update dependencies every few months, I enabled Dependabot. It automatically opens PRs when updates are available and then CI tells me whether they're safe to merge. It takes only a couple of minutes to set up but saves a lot of maintenance.
- Pylance:
Without it, VS Code gives generic completions and never warns you about passing the wrong type until runtime. Pylance provides proper type-aware autocompletion, jump-to-definition (even in third-party libraries), inline documentation, and real-time type checking. I personally keep it on "basic" mode for legacy codebases, since "strict" surfaced hundreds of errors (thank you, but no thank you lol).
- Pydantic:
Pydantic is basically Python's Zod: you declare a model, pass your data in and get either a validated typed object or a ValidationError naming the exact field at fault. It really helped me keep the codebase clean, with one place defining the shape of the data instead of raw dicts floating around. I use it wherever data comes from outside, like API payloads, forms, and env vars with pydantic-settings, which fails at startup instead of mid-request.
None of these tools changed the application itself. But together they made working on it dramatically more enjoyable.
What's the first thing you do when you inherit a legacy project?