Repository navigation
Import-sorting (a ruff approximation of isort) #465
Description
Activity
(I continually dip my toe into Rust and even a little PyO3 as I work on pantsbuild) Perhaps one day I'll pick this up myself 😉
Yes! I want to do this. I've been a little intimidated by the sheer amount of functionality that's packaged with
isort, but we don't need to cover every possible setting right from the start.I'm not certain if this will fit into the same "autofixable lint error" pattern, or if it will be its own sub-command.
Reacted by Josh Cannon, Somraj Saha, David Harting, Ben Stähli, Anujith Singh and Leah EinhornA potential first approximation that delays dealing with the first vs. third party heuristic would be to sort only within import groups separated by existing blank lines. This would be
isort-compatible in that it preserves a superset of the possibleisortoutputs.Reacted by Charlie Marsh and Josh CannonI like this idea! In my case it's just Isort with the Black profile, that's pretty much what I would like. 🤓
Reacted by Josh Cannon, Charlie Marsh, Somraj Saha, Tomáš Linhart, Arne Christian Beer, Matthias Weigand and Lautaro Lombardi👍 I'm thinking that this should be a separate subcommand (
ruff isort). I've been meaning to introduce subcommands anyway (such that the currentruffcall becomesruff check; and mayberuff --fixbecomes a standaloneruff fix?) since it opens the door to bundling a lot more functionality intoruff. The downside is that this will likely be a breaking change in the CLI API.Can you explain a little why you think it'd need its own subcommand and not be folded (optionally, config-enabled) into
ruff fixorruff?From my perspective seems like I'd need to run two commands when one would be sufficient.
You're probably right that it could be folded into
rufforruff fix. I will aim for that outcome. My main concern is that in order to model this as an autofixable error under the current autofix API, I'll likely need to treat the entire unordered import section as a single violation with a single fix. And if there are unused imports in the import block, there will be conflicting fixes for that chunk of code, and we'll fail to autofix one of them due to the conflicting source code ranges (that is, we'll either fail to remove the import, or fail to reorder the block).However, these are really deficiencies in the autofix API and not essential blockers to adding import-sorting as a "fixable lint error". And moving
isortout to its own subcommand is arguably just a hack to get around these problems.(One solution to this problem, which I don't love but is arguably already needed, is to iteratively re-check and autofix errors until there are no more fixable errors. So, e.g., you'd run
ruff fixonce, then internally, we'd check the file, find the unused import and the unsorted import block, and remove the unused import; we'd then re-check the modified source code, find the unsorted import block, fix that, etc.)(Another solution is to try and develop a more semantically-aware fix API, or maybe something based on CRDTs (???), that lets you apply both "Reorder these statements" and "Remove this one statement" in a single pass.)
I have a suspicion that if you start with the slow-but-correct way, your bar-chart of performance will still hold very relevant.
And then when you figure out the fast-and-correct, everyone rejoices 😄
(I've started work on this tonight.)
Reacted by Nyakku Shigure, Sebastián Ramírez and Corentin BettiolNot that my opinion matters much. But I have to admit that I'm not a big fan of how isort sorts out-of-the-box. And prefer something like this:
[tool.isort] profile = "black" force_sort_within_sections = true # Don't group `from` imports separately order_by_type = true # Order by CONSTANT, CamelCase, snake_case
Mostly because it reduces noise in diffs when going back and forth between
import fooandfrom foo import barsince the import would remain on the same line.It's also bit jarring to make such a change, save, and then the line moves 10 lines up/down when you have "format on save" enabled in your editor.
But maybe that's just me 🤷♂️ People seems to like separating
importandfrom.Reacted by Phil ElsonI noticed that
isortseems to avoid mergingfromimports intentionally (this is thecombine-as-importssetting).That is,
isortdoesn't modify this block:from .param_functions import Body as Body from .param_functions import Cookie as Cookie
Whereas, if you omit the
asalias, like so:from .param_functions import Body from .param_functions import Cookie
Then
isortgives you:from .param_functions import Body, Cookie
Here's an example from FastAPI (\cc @tiangolo):
There are some issues in isort suggesting that they wanted to make this the default (PyCQA/isort#1305, PyCQA/isort#1812).
I'm partial to making this Ruff's default, but curious if others feel strongly?
As long as
from .param_functions import Body as Body from .param_functions import Cookie as Cookie
became
from .param_functions import ( Body as Body, Cookie as Cookie, )
and that still is kosher from
mypy's "reexport" stance, I'd +1 the change.
(I usually see in our codebase)
from .param_functions import Body as Body # re-export from .param_functions import Cookie
which I'd kinda prefer:
from .param_functions import ( Body as Body, # re-export Cookie )
but I understand if that's prohibitively difficult to get right 😉
14 remaining items
That's awesome! Thanks @timabbott! Made my day :)
I saw Ruff insert a line between import and from import statements:
import Cython.Compiler.Options from Cython.Build import build_ext, cythonizeHow can this be avoided, I am on version v0.0.286
@MehulBatra perhaps you're looking for
isort-lines-between-types? I'd suggest reading the isort settings documentation there.How does one enable ruff's isort support? I have a project in which
ruff format .works fine in general, but imports are not being sorted. I'm running ruff v0.1.3.ruff .also detects no issues.Even import blocks as gross as
import jax import copy from typing import Optional import jax.numpy as jnp import jax.dlpack import torch import math import functools
are immune to ruff's import formatting. OTOH, running
isort --dont-follow-links .works just fine.Reacted by Fred Bunt@samuela - Import sorting is currently part of the linter (
ruff check --fix) rather than the formatter. So in this case, you'd want to add the following to yourpyproject.toml:[tool.ruff.lint] # Enable the isort rules. extend-select = ["I"]
Or, e.g.,
ruff check --select I --fix /path/to/file.py.(We're continuing to discuss whether import sorting should be part of the formatter (
ruff format), but it's a little tricky because -- unlike the rest of the formatter -- import sorting can actually change the behavior of your code.)Reacted by Samuel Ainsworth, Stoyan, Fred Bunt, Luis Benitez, Apinan Yogaratnam, asmith26 and Jen AlchimowiczThanks so much @charliermarsh ! Using
[tool.ruff.linter], gave me an error:unknown field 'linter', but[tool.ruff] # Enable the isort rules. extend-select = ["I"]
did the trick for me.
Thanks for all your hard work on ruff! 🏄♀️
Reacted by Ali Cirik and Merci BacOof, sorry, it's
tool.ruff.lint(buttool.ruffalso works for compatibility -- so either is fine). Will edit my original message. Really glad to get this working for you!Reacted by Samuel AinsworthThanks so much @charliermarsh ! Using
[tool.ruff.linter], gave me an error:unknown field 'linter', but[tool.ruff] # Enable the isort rules. extend-select = ["I"]
did the trick for me.
Thanks for all your hard work on ruff! 🏄♀️
Sorry if I am asking a dumb question, but where in the documentation can I find an explanation of this behaviour and why it works?
Not dumb at all. The generated documentation for that field is here: https://docs.astral.sh/ruff/settings/#extend-select. And a prose write-up on rule selection is here: https://docs.astral.sh/ruff/linter/#rule-selection.
(
extend-selectis likeselect, except it adds to the list rather than replacing it -- soextend-select = ["I"]enables the isort rules on top of the default rules, whileselect = ["I"]would enable only the isort rules.)(You can use either
[tool.ruff.lint]or[tool.ruff]right now. We're moving towards the former, but the latter is still supported and appears in all the docs at the moment.)Reacted by Niklas Alexander ShernAny chance we might implement isort's Auto-comment import sections?
Some projects prefer to have import sections uniquely titled to aid in identifying the sections quickly when visually scanning. isort can automate this as well. To do this simply set the
import_heading_{section_name}setting for each section you wish to have auto commented - to the desired comment.For Example:
import_heading_stdlib=Standard Library import_heading_firstparty=My Stuff
Trying with ruff:
[tool.ruff.lint.isort] import_heading_stdlib = "Standard Library"
ruff check . --fix ruff failed Cause: Failed to parse /Users/***/***/***/pyproject.toml Cause: TOML parse error at line 61, column 1 | 61 | [tool.ruff.lint] | ^^^^^^^^^^^^^^^^ unknown field `import_heading_stdlib`, expected one of ...
Reacted by Alexander LeyIf somebody is wondering how to add this in pre-commit, use this
- repo: https://github.com/charliermarsh/ruff-pre-commit rev: v0.2.0 hooks: - id: ruff args: ["check", "--select", "I", "--fix"] - id: ruff-formatReacted by Samuel Ainsworth, rosmur, isaaingl and Corentin Bettiol[tool.ruff.lint] # Enable the isort rules. extend-select = ["I"]
This works for both ruff-pre-commit with example configuration and ruff-vscode.
@Insighttful see this issue for tracking: #6371
Reacted by Insighttful and Corentin Bettiol@nik-hil I think that format is now updated? I get an error:
warning: The top-level linter settings are deprecated in favour of their counterparts in thelintsection. Please update the following options inpyproject.toml`:- 'per-file-ignores' -> 'lint.per-file-ignores'
I:1:1: E902 No such file or directory (os error 2)
Found 1 error.`
- 'per-file-ignores' -> 'lint.per-file-ignores'
isortdoes have some wacky heuristics to determine first v third party, but ultimately I'd love to elide the import-sorting it does withruff⚡To me, doesn't have to be 1:1, so long as the behavior is there for import sorting/grouping I'm happy 😄
If we want to keep this in the realm of
flake8, it'd be flake8-import-order with a fixer 😉