Even the Pylint codebase uses Ruff
github.com
github.com
There are a couple of drawbacks to Ruff that mean it may not be right for everyone: since it’s a compiled tool, it’s harder to add custom rules — you need to fork the project to add your own, and also you need to know some Rust.
When checking the Pylint codebase it makes sense to use Ruff as well as Pylint — it’s a good sanity check that Pylint’s own tooling is not letting issues through that another tool is catching.
Some teams use Ruff alongside Pylint, some have replaced Pylint with Ruff entirely (Dagster is a good example [2]), especially those already using a type checker alongside their linter.
[1] https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-pyli...
By the time pylint finishes, I'll have already fixed most of the low-hanging fruit that pyflakes found, and then I look through the pylint messages to see if there's anything additional.
There's very little waiting for pylint that way.
I’ve used the former system (compiling things together in a custom build) in a Rust-based static analysis tool I’ve created. SWC uses the latter system, requiring plugins to be authored in WASM.
Then somebody did it in Rust, where it's faster.
There's still a tradeoff: not as dynamic.
/s
The main reason given was that each plugin has to reimplement a bunch of the same stuff slightly differently (like finding public methods eg) and keeping it all internal avoids this and keeps things speedy
That might not be unsurmisable, given that most don't write their own rules (just combine, configure, enable/disable existing ones), and that in could be possible to add to Ruff a way to write rules in a DSL (without knowing Rust itself, and without the need recompile).
But if you start going down that route, isn’t it likely that you’d end up making Ruff into a full-blown interpreter?
Or that you end up embedding an interpreter into Ruff. Such as for example.. Python.
At either of which point, the motivation to use Ruff instead of e.g. Pylint might begin to disappear?
Not any more than with any other DSL. You can always stop at a subset enough to handle expressing linting rules. Or expressing many linting rules. Doesn't need to be able to express every possible rule -- which is the reason you'd make it a full blown interpeter. Pareto and all.
Even if that's not possible (and you need turing completness to handle custom rules), then:
>At either of which point, the motivation to use Ruff instead of e.g. Pylint might begin to disappear?
Well, Ruff would still have crazily faster parsing and built-in rules. It's not like you'd be forced to use custom interpreted rules.
And even in that case, a purpose-specific built interpeter, even if general, could be way faster (because it's optimized for a specific task and doesn't need to carry garbage as the GIL and other CPython decisions), and with better task-focused primitives, compared to embedding CPython.
I also think that these rewrites are throwing the baby out with the bathwater. Especially in projects related to code quality and analysis, things like widespread adoption and issues going back almost a decade are much more important than raw performance.
For every aspect of functionality, there is a heavy baggage of quirks, bugs, edge cases etc. I am not convinced that a rewrite of a tool that is still in development and still limited to a subset of functionality is able to capture nearly a decade of community work.
It's true that the folks who find it most helpful are the ones with large Python projects. But these are sometimes very important, widely used projects. See the testimonials from users (https://beta.ruff.rs/docs/#testimonials)
> widespread adoption
This makes little sense in the context of a linter. As long as my linter implements all the popular rules and my code is kept tidy, what's the issue? What does it matter if there's widespread adoption or not?
> still limited to a subset of functionality
You have a link to some functionality you're missing and necessary for your workflow? Or is this more of a theoretical concern?
Sorry if this comes across as rude.
There is no support for all syntax features of the current language specification and reference implementation. There is also no legacy support. Again, especially in projects concerned with code quality and analysis, building is not more important than long-term maintenance: Both language specification and reference implementation are constantly evolving. The current approach relies on an upstream interpreter and thereby inherits idiosyncratic divergences and limitations.
There is no extensibility. The majority of projects are neither public nor frameworks. Often projects employ a number of frameworks each with associated dialects, with both frameworks and dialects also constantly evolving. The current approach to extensibility is approximately one person manually rewriting rules individually from heterogeneous projects that span several contributors over several years. There is already outstanding maintenance work on manually introduced rules, only a subset of which gets flagged by a fraction of adopters. There is even outstanding work on feature parity.
Not a snowflake, just a Python web app.
EDIT: Missed your point about caching already-linted file. In that case maybe performance doesn’t matter.
> After all, performance optimizing a tool where N is too small to matter is the root of all evil.
Picking a third-party tool that does what it does and happens to be relatively fast. Where’s the evil in that?
The max we have ever had is 3 developers at a time, so if we were a 100 or 1000 person company, I bet it's a bit of time.
yeah, if only that was the case. Even if the file hasn't changed if it's dependencies have changed you need to lint again. And doing dependency analysis on most weirdly coupled codebases out there, is not trivial nor accurate tbh.
And not running a good linter and letting a bug through is the most shit feeling you will have in your life. Trust me on that.
For linters that perform type inference, fanout means having to relint files that might not have changed. But many linters don’t need the full graph — for example, file formatting problems aren’t subject to fanout.
Modern Java runtimes, .NET, Julia, and Android all make use of them.
Pylint is rough to make faster, party from the existing structure, but honestly at one point you are just paying a huge cost for traversing wide ASTs and doing all this work, and then paying extra for the Python object model.
In my profiling of Pylint, I've found some fixes, and I am curious about what something like slots or mypyc could do to it... but I'm also very partial to just having a system language implementation.
People talk about ease of contributing as a big advantage for these kinds of tools to be written in the language they are working on. Personally I've found that people who like messing with tools like the idea of learning languages to contribute.
My one kind of reservation about Ruff is that the code base is designed as "run each checker as its own function". My suspicion is that this leads to a performance ceilings as each checker is traversing the AST looking for its flavor of thing. Visitor patterns (theoretically!) allow for us to run checkers in parallel nicely. Granted the Ruff pattern allows for parallel work as well (and you can share all of the AST data between threads anyways), so maybe its "right". It definitely looks easier to maintain!
That would allow a lot of caching optimisations, as you can "index" each file in isolation.
[1]: https://github.blog/2021-12-09-introducing-stack-graphs/
[1] https://github.com/PyO3/pyo3
[2] https://safjan.com/rustification-of-python/#drawbacks-and-co...
too bad it doesn't appear to support flake8 plugins and only has a small subset hardcoded into it. if it could support arbitrary flake8 plugins I would move SQLAlchemy to this tool immediately.
the main reason people want to move off flake8 is not so much a speed issue but that its author refuses to allow it to use pyproject.toml files for configuration, meaning every project's configuration has to remain half-broken using obsolete setup.cfg files. A very silly situation.
If there are plugins that would be particularly impactful for you, I'd be happy to take a look at adding them. (We support some of the plugins you use in SQLAlchemy, like flake8-docstrings and flake8-builtins, but not flake8-rst-docstrings which I'm guessing is a blocker.)
flake8-import-order
flake8-builtins
flake8-future-annotations
flake8-docstrings
flake8-rst-docstrings
flake8-import-singleThat might be equivalent to using Ruff's isort implementation with `force_single_line` but maybe that comes with other implications that you don't want for SQLAlchemy.
My Rust skills are certainly very lacking but if there is some big list of arbitrary flake8 plugins that there is a desire to have ported, that sounds like it could be a relatively easy place to get one’s feet wet.
pyproject thing, he just doesn't want to "give in", there's zero technical issue
These are relatively speaking small codebases, and the speed improvements really don’t make a difference for us, but with trying to get our own projects in line with current standards flake8’s refusal to support it was the final nail in the coffin.
I think the point was reasonable when pyproject.toml first came about and parsing it was a bit more of a Wild West, I can certainly understand the unwillingness to try and support it then. Now with Python itself having toml support in the standard library, and every other major tool standardizing on it, not to mention TOML being the de-facto config file for Rust projects. It just seems like a bizarre hill to die on to me now.
It’s not as if the work hasn’t been done, there are forks of flake8 with pyproject.toml support, but as far I’m aware multiple PRs with support for it have been denied.
Though this is something that keeps coming up on Reddit as well, I assume there's a reason why HN went with this design.
> How do I make a link in a text submission?
> You can't. This is to prevent people from submitting a link with their comments in a privileged position at the top of the page. If you want to submit a link with comments, just submit it, then add a regular comment.
If you want to add more context, add it in a comment. If it’s good people will upvote it.
Another feature I hope to see in Ruff is to "score" the code. With pylint, I can have a CI rule to make the lint check pass if it scores 8/10 or above, or even a lower score so that co-workers who resent using linters can start using them.
[1] We Rewrote Our Project With Rust… and It’s Almost 40X Faster. https://medium.com/better-programming/40x-faster-we-rewrote-...
It seems so.
Our codebase is somewhere between 200k and 300k LOC. Running the flake8 suite takes minutes. We haven't integrated ruff, but during a test run it ran in less than a second, IIRC.
So, with, say 20-30 commits per day, that's about an hour, every day. Sure, it's not like we're usually sitting there twiddling our thumbs during builds, but sometimes we actually do have to wait for a build to get ready. Just the waiting time during those rare occasions add up.
[edit]: flake8, remarkably, dropped roughly an order of magnitude in performance between version 2 and 3. So that didn't help.
Sorry for the drive by, but did you try bisecting the flake8 change that slowed it down? Unless there were thousands of commits between releases, it would only take <10 runs to bisect, an automated bisect should be under an hour with a test case that takes a few minutes. Could be an interesting thing to try?
In either case, the information wouldn't be all that useful. We run our stuff off of Debian packages (yes, we're old school), and we're not particularly interested in maintaining our own fork.
[0] https://flake8.pycqa.org/en/latest/user/python-api.html#lega...
See example graph: https://user-images.githubusercontent.com/1309177/212613422-...
For those of us who use pylint and flake8, this is a heaven-sent tool!
Do you use pylint ? If yes what are the important pylint checks you missed in ruff?
At this point (IMHO) ChatGPT does not make a meaningful difference in the production of software, aside from individual workflow preferences. The reason being ChatGPT will produce a lot of computer code which is very likely to be subtly incorrect in difficult to spot places.