Skip to content

Conversation

@sbidoul
Copy link
Member

@sbidoul sbidoul commented Jan 15, 2024

Coming from #12454 (comment) I made a quick try to use ruff-format instead of black.

Copy link
Member

@pfmoore pfmoore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some nitpicks, some cases where I dislike ruff's choices, and a couple of flat-out issues that need addressing (probably by ruff). Specifically, the failure to respect # fmt: off, and the way #type: ignore comments are changed to affect different code, are showstoppers IMO.

Also, it feels like ruff's line length setting is being applied differently than black's (or the settings themselves differ). It's not a disaster, but a couple of the wrapping points seem to have changed for no obvious reason.

os.path.exists(options.target_dir) and
not os.path.isdir(options.target_dir)
os.path.exists(options.target_dir)
and not os.path.isdir(options.target_dir)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bothers me because it's in a # fmt: off block. Does ruff not support that directive?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ruff is more limited in where it supports supression statements: astral-sh/ruff#6338

I suspect this is a location where it does not support them, ruff has been considering adding a warning for this case: astral-sh/ruff#6611

value: Union[str, List[str]] = [
sanitise_header(v) for v in msg.get_all(field) # type: ignore
sanitise_header(v)
for v in msg.get_all(field) # type: ignore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be a semantic change. If the # type: ignore directive is per-line (rather than per-expression) then the sanitise_header(v) will now be type-checked.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mypy is happy with it, so it should be fine

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems this is an implementation based semantic (i.e. no standard), for MyPy you should be getting a consistent behavior on Python 3.8+ (but potentially not if you were to run it on 3.7, which Pip's CI doesn't do): python/mypy#6648

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may have misunderstood my comment (or I may have misunderstood your reply). From PEP 484, "The # type: ignore comment should be put on the line that the error refers to". This suggests that if there were an error in line 67 (sanitise_header(v)), the ignore comment wouldn't apply to it now, because that ignore is on line 68.

I know I'm being very pedantic here, but I'm bothered by the general idea that ruff is failing to reformat in a way that's semantically identical.

The case in test_utils.py below is even more problematic, IMO.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems this is an implementation based semantic (i.e. no standard)

Well, PEP 484 states the behaviour. But I agree that there's a lot of typing behaviour that's highly dependent on the type checker you use. I have my issues with that, but they aren't relevant here. What is relevant is that ruff shouldn't be making changes it can't be 100% certain are semantically neutral.

Please understand, I'm not complaining about ruff here - I'm a huge fan of it. But the formatting capability is relatively new, and I'm not sure I want pip to be an early adopter if there are still issues like this that need to be discussed and resolved (potentially between the formatter and type checker communities - expression-based type ignores may well be a far better resolution, so ruff may be making the right call here).

"ERROR: You must pass --bash or --fish or --powershell or --zsh"
in result.stderr
), ("completion alone failed -- " + result.stderr)
), "completion alone failed -- " + result.stderr
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I prefer black's approach of leaving the parentheses alone here. But I guess the whole point of using an automated formatter is that I'm supposed to not have opinions like that 🙄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This particular case id consider a potential bug report for ruff as the parenthesis helps reading

But i also recommend switching to f strings for the particular expression for even better reading

"autocomplete function completed <file> or <dir> that "
"should not be completed"
)
), "autocomplete function completed <file> or <dir> that should not be completed"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... and even more so here. I much prefer the split and parenthesised version. But whatever, I guess.

== (
# Use new config file
get_configuration_files()[kinds.USER][1]
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely worse than the black choice.

subdir_path.mkdir()
path = str(subdir_path)
os.chmod(path, stat.S_IWRITE)
os.chmod(subdir_path, stat.S_IWRITE)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did ruff do this, or was it a manual change? I'm very concerned if this was a ruff change. If it was manual, it's unrelated to this PR, isn't it?

Copy link
Member Author

@sbidoul sbidoul Jan 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manual change indeed. I did it in a separate commit. I detected it because ruff split it in 3 lines which revealed that the # type: ignore was addressing two separate typing issues. So thanks ruff in this case.

mock_func, path, sys.exc_info() # type: ignore[arg-type]
mock_func,
subdir_path,
sys.exc_info(), # type: ignore[arg-type]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I'm not convinced that this doesn't change what's affected by the # type: ignore directive.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, the correct reformatting here has to be

                mock_func,  # type: ignore[arg-type]
                subdir_path,  # type: ignore[arg-type]
                sys.exc_info(),  # type: ignore[arg-type]

This seems to me like a straight bug in ruff.

Copy link
Member

@notatallshaw notatallshaw Jan 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There appears to be a related discussion on ruff side here: astral-sh/ruff#8286 (comment)

But no specific issue open on ruff side that I can find which exactly matches your concern.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a note about this case to that issue.

silent=True,
)
cmd = ["git", "tag", "-m", message, tag_name]
session.run(*cmd, external=True, silent=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manual change, unrelated to ruff? I can accept that the changed version is better, but IMO we shouldn't be mixing changes to the automation and manual reformatting in the one PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I should have done that in another commit.

".", str(git_checkout_dir),
".",
str(git_checkout_dir),
# fmt: on
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like ruff is flat-out ignoring #fmt: off here. That's a ruff bug (or at best, an annoying decision to not respect the standard convention) and should be reported to ruff. IMO this is a blocker to us adopting ruff.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is Ruff needing the comments at statement level.

See https://docs.astral.sh/ruff/formatter/#format-suppression which says instead of:

[
    # fmt: off
    '1',
    # fmt: on
    '2',
]

Do:

# fmt: off
[
    '1',
    '2',
]
# fmt: on

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. I still think it's a problem with ruff, though. At the absolute minimum it should warn if it's ignoring formatting directives. (Specifically if it's ignoring directives that other formatters like black would respect).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They have an open issue on warning, I'm sure sure supportive feedback would be welcome: astral-sh/ruff#6611

@pfmoore
Copy link
Member

pfmoore commented Jan 15, 2024

Thanks for doing this - you beat me to it 🙂

@notatallshaw
Copy link
Member

notatallshaw commented Jan 15, 2024

I don't think linting rule ISC001 should be turned off, it's a valuable rule that has caught real bugs in code bases I work with.

I understand that it may require a PR author a manually fix to the code to get a successful pass of both the ruff linter and the ruff formerter with rule ISC001, and therefore ruff complains about it, but that's already the case with running the ruff linter and running black, just black isn't aware of this linting rule.

FYI the discussion on ruff side is here: astral-sh/ruff#8272

@sbidoul
Copy link
Member Author

sbidoul commented Jan 15, 2024

I don't think linting rule ISC001 should be turned off, it's a valuable rule that has caught real bugs in code bases I work with.

It's ruff that recommended to disable it so I complied :).

@notatallshaw
Copy link
Member

notatallshaw commented Jan 15, 2024

It's ruff that recommended to disable it so I complied :).

Yes, my comment explains why ruff warns about it and the reasoning I don't think it should be turned off:

I understand that it may require a PR author a manually fix to the code to get a successful pass of both the ruff linter and the ruff formerter with rule ISC001, and therefore ruff complains about it, but that's already the case with running the ruff linter and running black, just black isn't aware of this linting rule.

And ruff is considering turning the warning off, see: astral-sh/ruff#8272

@notatallshaw
Copy link
Member

notatallshaw commented Jan 10, 2025

ruff has now removed the ISC001 incompatibility warning (astral-sh/ruff#15123) as of ruff 0.9.0 it will format the internals of f-strings, and will safely merge them if doing so is below the defined line length.

Further, I am hopeful of astral-sh/ruff#15378 addressing @pfmoore's main concern.

However, ruff has moved to the 2025 style ahead of black, if there is still motivation to move to ruff I would reccomend waiting till black 25.1.0 and then comparing with ruff.

@notatallshaw
Copy link
Member

Further, I am hopeful of astral-sh/ruff#15378 addressing @pfmoore's main concern.

It would seem the answer is no, I ran ruff 0.9.3 on top of #13193, and there are still the same issues with # fmt: off / # fmt: on and type-ignore.

One advantage ruff provides now is its ability to format f-string expressions, and concatenate different string literal types. I'm not sure if there's a roadmap for black to add support for that, seems non-trivial.

@ichard26
Copy link
Member

The Ruff formatter seemed to have picked up more divergences from Black since this PR was opened. https://github.com/pypa/pip/compare/main...ichard26:pip:ruff-format?expand=1

IME, Black is fast enough that a switch wouldn't be that beneficial.1 Is anyone still interested in making this switch.

Footnotes

  1. Of course, you could consider me biased as I used to be a maintainer of Black.

@notatallshaw
Copy link
Member

notatallshaw commented Jul 12, 2025

In my personal use of ruff I really like it's ability to concatenate string literals and f-strings, as well as handle formatting inside f-strings expressions.

However it doesn't look like the ruff Python parser has any plans to handle preserving the comment position inside an expression or disabling formatting mid expression. (As a side note this will come up as an issue with ty and type ignore comments, Astral will need to provide some other way of ignoring types).

And I agree the speed difference between black and ruff is not significant enough for the pip codebase. I think this can be closed for now.

@sbidoul sbidoul closed this Jul 12, 2025
@sbidoul sbidoul deleted the use-ruff-format branch July 12, 2025 10:26
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 28, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants