ruff
ruff copied to clipboard
Implement pyupgrade
- [x] Set literals
- [x] Dictionary comprehensions
- [x] use
datetime.UTCalias - [x] Format Specifiers
- [x] printf-style string formatting
- [x] Unicode literals
- [x] Invalid escape sequences
- [x]
is/is notcomparison to constant literals - [x]
.encode()to bytes literals - [x] extraneous parens in
print(...) - [x] unittest deprecated aliases
- [x]
super()calls - [x] "new style" classes
- [x] rewrites class declaration
- [x] removes
__metaclass__ = typedeclaration
- [x] forced
str("native")literals - [x]
.encode("utf-8") - [x]
# coding: ...comment - [x]
__future__import removal - [x] Remove unnecessary py3-compat imports
- [x] import replacements
- [x] rewrite
mockimports - [x]
yield=>yield from - [x] Python2 and old Python3.x blocks
- [x] remove
sixcompatibility code - [x]
openalias - [x] redundant
openmodes - [x]
OSErroraliases - [x]
typing.Textstr alias - [x] Unpacking list comprehensions
- [x] Rewrite
xml.etree.cElementTreetoxml.etree.ElementTree - [x] Rewrite
typeof primitive - [x]
typing.NamedTuple/typing.TypedDictpy36+ syntax- [x]
typing.NamedTuple - [x]
typing.TypedDict
- [x]
- [x] f-strings
- [x]
subprocess.run: replaceuniversal_newlineswithtext - [x]
subprocess.run: replacestdout=subprocess.PIPE, stderr=subprocess.PIPEwithcapture_output=True - [x] remove parentheses from
@functools.lru_cache() - [x] replace
@functools.lru_cache(maxsize=None)with shorthand - [x] pep 585 typing rewrites
- [x] pep 604 typing rewrites
- [ ] remove quoted annotations
Please add the tests from pyupgrade to assure equivalent behavior.
I guess I can just pick some rules in here for implementation?
I guess I can just pick some rules in here for implementation?
Yes, can you comment on the one you choose so there is no conflict?
I'll get started on six code
I'll look at yield -> yield from.
I'd like to knock out a few more of these. Now that the LSP supports autofix, they're even more valuable.
I can do Remove unnecessary py3-compat imports and import replacements.
I can dig into typing.Text str alias.
I am going to take these two next:
- subprocess.run: replace universal_newlines with text (UP021)
- subprocess.run: replace stdout=subprocess.PIPE, stderr=subprocess.PIPE with capture_output=True (UP022)
Today I will work on:
- Rewrite xml.etree.cElementTree to xml.etree.ElementTree (UP023)
- OSError aliases (UP024)
- Unicode literals (UP025)
Going to start on rewrite mock imports (UP026)
I will take .encode() to bytes literals (UP027)
Edit, looks like someone already took this (UP012)
I will take: Unpacking list comprehensions (UP027)
I will take: yield => yield from (UP028)
I will take: Format Specifiers (UP029) next
I will take: printf-style string formatting (UP031) next. I will have this done by Sunday.
I will take: f-strings (UP032)
I will take: extraneous parens in print(...) (UP033)
I will take: import replacements (UP035)
Import replacements might requiring auditing some of what we already do. Like, we do some of the replacements as part of the other Pyupgrade rules. I'm not totally sure what we're missing. (I'm also worried that it'll be a really tricky one to implement :joy:)
I am probably going to regret saying this, but this seems like its not too challenging. The process I was thinking is:
- Search this dict for matches (converted to a rust hashmap)
- Replace any specific items that match. If its a muli-line then we can move the replaced items to a new line. I would use libcst imports here
- Return the new strings
I will take: Python2 and old Python3.x blocks (UP037)
I will take: remove quoted annotations (UP038) AKA the LAST one 🎉🎉🎉.
@colin99d - Awesome. Like pyupgrade (IIUC), let's only enforce that when from __future__ import annotations is present.
I'm not sure where is a good place to drop this, but it would be great to have pydowngrade (AFAIK it's not a thing yet) along with pyupgrade.
This will allow to develop a project with newest set of features, and maintain compatibility with previous python version by downgrading certain things when building a wheel for specific version.
Hope it's not completely out of place 😄
Hello Bobronium,
I love the idea! As a developer on a python library myself, it is annoying not being able to add the newest features in my code, because some people are still on python 3.7. However, I see two issues with this idea:
- This would take a while to code (probably just as long as coding all the pyupgrade features), but with a much smaller potential use case.
- Some of the lints would not be able to be reverted, for example, if you ran the lint that removes "u" before a string, then ruff would not know which strings to add it back to.
I think a much better implementation for this would be to have your CI that uploads pip take the version that works for your oldest members, and then upgrade it to the correct version for each user group downloading it. While this doesn't help the developers, it would mean all users of the code get the newest features.
In testing this out using Ruff 0.0.251 and enabling "UP" rules, the first pyupgrade feature mentioned in the original checklist ("Set literals", https://github.com/asottile/pyupgrade#set-literals) does not appear to be handled and is not listed under the rules here https://beta.ruff.rs/docs/rules/#pyupgrade-up, despite being checked off above. Perhaps it was actually never implemented? Should I file a new issue for this?
@sjdemartini - I think those rules are handled instead by flake8-comprehensions rules, like C405.
Aha, indeed, thank you Charlie!
No prob! Same is probably true of dictionary comprehensions.
First of all, thanks for the excellent tool! Loving it 😍
After reading those last comments here, I got curious on exactly which extra rules besides UP I should use to get the full pyupgrade functionality in ruff.
I haven't found that information in the docs, so I did some testing and the closer I got was:
ruff --target-version py311 --select UP,C401,C402,C403,C404,C405,F632,W605 --fix foo.py
Description of those rules:
# Activate all the rules that are pyupgrade-related
select = [
"UP", # pyupgrade
"C401", # flake8-comprehensions: unnecessary-generator-set
"C402", # flake8-comprehensions: unnecessary-generator-dict
"C403", # flake8-comprehensions: unnecessary-list-comprehension-set
"C404", # flake8-comprehensions: unnecessary-list-comprehension-dict
"C405", # flake8-comprehensions: unnecessary-literal-set
"F632", # pyflakes: is-literal
"W605", # pycodestyle: invalid-escape-sequence
]
The only pyupgrade feature not supported in ruff is remove six compatibility code.
The only ruff UP rule not supported in pyupgrade is UP038: non-pep604-isinstance: Use X | Y in {} call instead of (X, Y).
On a side note, since this PR is highly ranked in the "ruff pyupgrade" search, maybe we could add the ruff rules IDs to the items in this PR description, exactly as being done in "Implement Pylint" (#970)? This way people can better correlate the pyupgrade feature name with the ruff rule. This may help.