Range Suppression in Ruff v0.16.0: Guide and Usage

Ruff v0.16.0

Ruff just jumped from 59 to 413 default rules. That is a massive leap in a single update, and it fundamentally changes how much noise you can scrub from your codebase in a few milliseconds. I've seen a lot of linting tools try to be "comprehensive," but usually that just means they annoy you with false positives until you eventually turn them off.

The real win here isn't actually the volume of rules. It's the new range suppression. You can finally wrap a specific block of code with ruff: disable and ruff: enable without having to pepper your entire file with inline comments. It's a small ergonomic change, but it's the kind of thing that makes a tool actually usable in a large production environment.

Most of these new rules are helpful, but adding 350+ checks to your CI pipeline at once is a great way to break every single build in your company. The question is whether you should just flip the switch or spend your weekend manually auditing a thousand new warnings.

The speed gap is the real story

The real value of Ruff isn't just that it's fast, it's that it changes when you decide to run your linter. When a tool is 10 to 100x faster than Flake8 or Black, linting stops being a chore you delegate to a CI pipeline and becomes something that happens in real-time as you type. For a module with 250k lines of code, waiting for a slow linter is a productivity killer. Ruff cuts that wait time down to nearly nothing, which means you can actually keep your linter running on every file save without your IDE lagging.

This shift from "scheduled CI checks" to "real-time local linting" is a huge win for developer experience. Most of us have spent years ignoring linting errors until the GitHub Action fails, then spending ten minutes fixing commas and imports. Ruff removes that loop. It's also helpful that it enables 413 rules by default, so you get a high standard of code quality without spending an afternoon fiddling with a .toml file.

Installing it is straightforward. If you're using uv, you can run it without even installing it globally:

uvx ruff check .

The speed is a result of it being written in Rust, but the practical result is just a lack of friction. I'm still skeptical about how many "all-in-one" tools actually survive long-term, but the performance gap here is too large to ignore.

import math # ruff: ignore[unused-import]

Precision control and range suppression

Global linting rules are great until they hit a weird edge case in your codebase. You don't want to disable a rule for the entire project just because one function needs to break a convention. The best way to handle this is by pairing ruff: disable and ruff: enable. This creates a narrow window where the rule is ignored, preventing the "exception leak" that happens when you disable a rule at the top of a file and forget to turn it back on.

import unused_module_for_type_checking

def my_function():
    return "This is now linted again"

The formatter is a different story. While the linter handles logic and style rules, the formatter handles the actual layout of the code. There are times when Ruff's default formatting ruins a complex matrix or a carefully aligned list. In those cases, use fmt: off and fmt: on. This part is genuinely confusing because people often try to use linter disables to fix formatting issues, but they're two different engines. The formatter doesn't care about ruff: disable.

matrix = [
    [1, 0, 0],
    [0, 1, 0],
    [0, 0, 1],
]

Balancing these local overrides with global config is a trade-off. If you find yourself adding the same # ruff: disable comment in ten different files, your global rules are too strict. It's better to move the exception into your pyproject.toml and ignore the rule for the whole project than to clutter your source code with comments.

Expanding the default rule set

The addition of range suppression is a practical win. Being able to wrap a specific block of code in ruff: disable and ruff: enable instead of littering a file with a dozen single-line ignores is a quality-of-life improvement that actually respects how developers organize their code. It's a small change, but it removes a specific kind of friction that makes people want to turn off the linter entirely when things get messy.

I've seen the chatter about the breaking changes in v0.16. People are annoyed with the semver handling, and honestly, I get it. When a tool integrates itself into a CI pipeline, "fast iteration" can easily feel like "unreliable infrastructure." Astral is moving at a speed that favors feature velocity over stability. While the community is generally happy that development hasn't stalled post-acquisition, this tension between rapid updates and predictable builds is going to persist.

I suspect we'll see more of these "breaking" default rule expansions. The real question is whether the Ruff team can find a way to introduce new defaults without triggering a wave of CI failures every few weeks.

Practical usage and CLI automation

Range suppression is a small addition that solves a persistent annoyance. Before this, you had to sprinkle # noqa comments on every single line of a block to stop the linter from complaining. Now you can just wrap the section. It doesn't fundamentally change how Ruff works, but it makes the tool less intrusive when you're forced to write code that doesn't fit the style guide—like when you're interfacing with a legacy API that demands a specific, ugly format.

I see people on GitHub complaining about the semver violations in the v0.16 release cycle. They aren't wrong; breaking changes in a tool that's supposed to be a drop-in replacement for other linters is frustrating. But I think the friction is a byproduct of how fast Astral is moving. They're prioritizing velocity over a rigid versioning scheme, and for most users, the utility of the new rules outweighs the pain of updating a config file once every few weeks.

The bigger question is whether the acquisition by OpenAI changes the roadmap. People are optimistic, but I'm skeptical that a corporate parent will let them maintain this kind of "move fast and break things" pace for long. I suspect we'll see a shift toward more conservative release cycles as the user base grows and the cost of a breaking change becomes higher.

Conclusion

Ruff is still the fastest tool in the Python ecosystem—that 1000x speed gap isn't going away—but the focus is shifting from raw performance to precision. Range suppression is a small addition, but it solves the annoying problem of having to disable a rule for an entire file when you only have one messy block of code.

I'm still not entirely sure if we'll all actually use ruff: disable and ruff: enable pairs regularly, or if we'll just keep using the blunt instrument of file-wide ignores. But for those of us fighting with specific formatting blocks, the control is finally there.

If you're on v0.15, just update to v0.16. The breaking changes are minimal, and the ability to surgicaly mute the linter is worth the update.