ruff icon indicating copy to clipboard operation
ruff copied to clipboard

Implement pyupgrade

Open JonathanPlasse opened this issue 2 years ago • 23 comments

  • [x] Set literals
  • [x] Dictionary comprehensions
  • [x] use datetime.UTC alias
  • [x] Format Specifiers
  • [x] printf-style string formatting
  • [x] Unicode literals
  • [x] Invalid escape sequences
  • [x] is / is not comparison 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__ = type declaration
  • [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 mock imports
  • [x] yield => yield from
  • [x] Python2 and old Python3.x blocks
  • [x] remove six compatibility code
  • [x] open alias
  • [x] redundant open modes
  • [x] OSError aliases
  • [x] typing.Text str alias
  • [x] Unpacking list comprehensions
  • [x] Rewrite xml.etree.cElementTree to xml.etree.ElementTree
  • [x] Rewrite type of primitive
  • [x] typing.NamedTuple / typing.TypedDict py36+ syntax
    • [x] typing.NamedTuple
    • [x] typing.TypedDict
  • [x] f-strings
  • [x] subprocess.run: replace universal_newlines with text
  • [x] subprocess.run: replace stdout=subprocess.PIPE, stderr=subprocess.PIPE with capture_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.

JonathanPlasse avatar Nov 20 '22 11:11 JonathanPlasse

I guess I can just pick some rules in here for implementation?

martinlehoux avatar Nov 22 '22 08:11 martinlehoux

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?

JonathanPlasse avatar Nov 22 '22 08:11 JonathanPlasse

I'll get started on six code

martinlehoux avatar Nov 22 '22 22:11 martinlehoux

I'll look at yield -> yield from.

squiddy avatar Nov 26 '22 13:11 squiddy

I'd like to knock out a few more of these. Now that the LSP supports autofix, they're even more valuable.

charliermarsh avatar Dec 02 '22 22:12 charliermarsh

I can do Remove unnecessary py3-compat imports and import replacements.

charliermarsh avatar Dec 04 '22 04:12 charliermarsh

I can dig into typing.Text str alias.

colin99d avatar Dec 26 '22 16:12 colin99d

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)

colin99d avatar Dec 26 '22 23:12 colin99d

Today I will work on:

  • Rewrite xml.etree.cElementTree to xml.etree.ElementTree (UP023)
  • OSError aliases (UP024)
  • Unicode literals (UP025)

colin99d avatar Dec 28 '22 13:12 colin99d

Going to start on rewrite mock imports (UP026)

colin99d avatar Dec 29 '22 20:12 colin99d

I will take .encode() to bytes literals (UP027)

Edit, looks like someone already took this (UP012)

colin99d avatar Jan 01 '23 13:01 colin99d

I will take: Unpacking list comprehensions (UP027)

colin99d avatar Jan 01 '23 17:01 colin99d

I will take: yield => yield from (UP028)

colin99d avatar Jan 01 '23 19:01 colin99d

I will take: Format Specifiers (UP029) next

colin99d avatar Jan 02 '23 17:01 colin99d

I will take: printf-style string formatting (UP031) next. I will have this done by Sunday.

colin99d avatar Jan 11 '23 01:01 colin99d

I will take: f-strings (UP032)

colin99d avatar Jan 15 '23 21:01 colin99d

I will take: extraneous parens in print(...) (UP033)

colin99d avatar Jan 16 '23 21:01 colin99d

I will take: import replacements (UP035)

colin99d avatar Jan 19 '23 22:01 colin99d

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:)

charliermarsh avatar Jan 19 '23 22:01 charliermarsh

I am probably going to regret saying this, but this seems like its not too challenging. The process I was thinking is:

  1. Search this dict for matches (converted to a rust hashmap)
  2. 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
  3. Return the new strings

colin99d avatar Jan 20 '23 03:01 colin99d

I will take: Python2 and old Python3.x blocks (UP037)

colin99d avatar Jan 23 '23 00:01 colin99d

I will take: remove quoted annotations (UP038) AKA the LAST one 🎉🎉🎉.

colin99d avatar Jan 29 '23 23:01 colin99d

@colin99d - Awesome. Like pyupgrade (IIUC), let's only enforce that when from __future__ import annotations is present.

charliermarsh avatar Jan 30 '23 00:01 charliermarsh

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 😄

Bobronium avatar Feb 02 '23 20:02 Bobronium

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.

colin99d avatar Feb 02 '23 20:02 colin99d

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 avatar Feb 22 '23 21:02 sjdemartini

@sjdemartini - I think those rules are handled instead by flake8-comprehensions rules, like C405.

charliermarsh avatar Feb 22 '23 21:02 charliermarsh

Aha, indeed, thank you Charlie!

sjdemartini avatar Feb 22 '23 21:02 sjdemartini

No prob! Same is probably true of dictionary comprehensions.

charliermarsh avatar Feb 22 '23 21:02 charliermarsh

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.

aureliojargas avatar May 13 '23 23:05 aureliojargas