Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adopting use of flake8 plugins #5537

Closed
pradyunsg opened this issue Jun 26, 2018 · 55 comments
Closed

Adopting use of flake8 plugins #5537

pradyunsg opened this issue Jun 26, 2018 · 55 comments
Labels
help wanted For requesting inputs from other members of the community type: maintenance Related to Development and Maintenance Processes

Comments

@pradyunsg
Copy link
Member

In general, there's some flake8 plugins that might be useful to us. In case of some of these plugins, it means adopting a newer code style so we should probably start with that discussion.

  • flake8-commas PyPI: enforce commas after the last item of multiline literals/function calls.
  • flake8-mutable PyPI: enforce not using mutable values as defaults
    • TODO: check if flake8 does this check by default
  • flake8-logging-format PyPI: check that strings aren't formatted while passing to logging (should depend on logging doing the interpolation)
  • flake8-pep3101 PyPI: enforces the use of .format instead of % in the codebase
  • flake8-gramex PyPI: checks for magic numbers and required encoding for calls to file opening functions + 2/3 more (but we'll ignore those)
    • It's documentation is lacking

I think some of these might be good additions. The only thing is that adopting them would mean going through the entire codebase to make things consistent according to them wherever there's a violation (which is kind of the point of doing this).

Thoughts @pypa/pip-committers?

@pradyunsg pradyunsg added the type: maintenance Related to Development and Maintenance Processes label Jun 26, 2018
@pradyunsg pradyunsg added this to the Internal Cleansing milestone Jun 26, 2018
@pradyunsg pradyunsg self-assigned this Jun 26, 2018
@xavfernandez
Copy link
Member

flake8-commas 👍
flake8-mutable and flake8-logging-format I'm wondering if pylint does not already do that (and hence, wouldn't we want to also use pylint in our lint target ?)
flake8-pep3101 I'm 0-.
flake8-gramex magic numbers and encoding seem like sensible checks.
I've also heard of flake8-bugbear with some additionnal checks: https://github.com/PyCQA/flake8-bugbear#list-of-warnings which currently output this list of warnings:

./src/pip/_internal/wheel.py:509:20: E741 ambiguous variable name 'l'
./src/pip/_internal/wheel.py:531:5: E722 do not use bare except'
./src/pip/_internal/wheel.py:656:17: E722 do not use bare except'
./src/pip/_internal/wheel.py:688:13: E722 do not use bare except'
./src/pip/_internal/wheel.py:701:9: E722 do not use bare except'
./src/pip/_internal/basecommand.py:243:9: E722 do not use bare except'
./src/pip/_internal/compat.py:220:13: E722 do not use bare except'
./src/pip/_internal/compat.py:231:13: E722 do not use bare except'
./src/pip/_internal/req/__init__.py:51:13: E722 do not use bare except'
./tests/unit/test_req_uninstall.py:124:9: E741 ambiguous variable name 'l'
./tests/unit/test_req_file.py:642:13: E722 do not use bare except'

@pradyunsg
Copy link
Member Author

Actually, all of those are errors from a newer version of flake8. I have a local branch that fixes that. It might also be a PR already but in case it isn't, I'll make one for that in the evening today.

(on mobile, it's 3am, I'm too sleepy to do anything)
(also hi o/)

@xavfernandez
Copy link
Member

Actually, all of those are errors from a newer version of flake8.

Indeed >.<

@pradyunsg
Copy link
Member Author

and hence, wouldn't we want to also use pylint in our lint target

Yeah. FWIW, pylint doesn't really have sane defaults and it just comes down to actually sitting down and deciding what checks should be a part of your code-style and then enforcing them. I'm personally not convinced that to do this though.

Though, I do think if someone wants to put in the effort to do that, it'd be a net positive at the end of it for everyone.

I have a local branch that fixes that.

#5543

@pradyunsg pradyunsg added this to To do in Internal Cleansing May 25, 2019
@pradyunsg pradyunsg removed their assignment Dec 14, 2019
@pradyunsg
Copy link
Member Author

Bumping this, to check if folks are still interested in these checks.

@pradyunsg
Copy link
Member Author

Noting flake8-printf-formatting from #6973.

@pradyunsg pradyunsg added the help wanted For requesting inputs from other members of the community label Feb 2, 2020
@pradyunsg
Copy link
Member Author

If anyone wants to work on this, the way we'd add a flake8 plugin to our linting setup, is to add an additional_dependencies key to our .pre-commit-config.yaml file:

        additional_dependencies: [
            'flake8-bugbear==19.8.0',
            'flake8-coding==1.3.2',
            'flake8-comprehensions==3.0.1',
            'flake8-debugger==3.2.1',
            'flake8-deprecated==1.3',
            'flake8-docstrings==1.5.0',
            'flake8-pep3101==1.2.1',
            'flake8-polyfill==1.0.2',
            'flake8-print==3.1.4',
            'flake8-quotes==2.1.1',
            'flake8-string-format==0.2.3',
        ]

Note: Do not use the exact versions/plugins as in the example above -- please check what plugins have been discussed in this issue and what their current versions are.

Relevant: pre-commit/pre-commit-hooks#311

@webknjaz
Copy link
Member

webknjaz commented Feb 4, 2020

@pradyunsg I recently discovered a flake8 plugin collection that I mostly like: https://wemake-python-stylegui.de/. It even supports a legacy-first mode (where it doesn't yell at you for the errors that existed prior to the integration of the linter).

@pradyunsg
Copy link
Member Author

Yea, I've seen that. Sadly, one of the objectives is to "Enforce python3.6+ usage". :(

@webknjaz
Copy link
Member

webknjaz commented Feb 4, 2020

Sadly, one of the objectives is to "Enforce python3.6+ usage". :(

Where? I'm pretty sure it's Python 2.7 compatible, I'm trying it out on Cheroot now. If there's some plugins that are incompatible, they are easily disabled.

@deveshks
Copy link
Contributor

Hi @pradyunsg

Is this something I can take up? I assume that after adding this plugins, we would also have to fix code which doesn't follow the conventions set by these.

@deveshks
Copy link
Contributor

deveshks commented Jun 4, 2020

Revisiting this issue, how would we want to add flake8 plugins mentioned in the issue?

Do we want to add them one at a time, fix the codebase to get rid of any warnings that the plugin generates and then move on to the next one?

@pradyunsg
Copy link
Member Author

Do we want to add them one at a time, fix the codebase to get rid of any warnings that the plugin generates and then move on to the next one?

Sounds about right to me. We'd basically need to install them as additional_dependencies (see https://pre-commit.com/#pre-commit-configyaml---hooks) to the flake8 pre-commit task.

@deveshks
Copy link
Contributor

deveshks commented Jun 4, 2020

Do we want to add them one at a time, fix the codebase to get rid of any warnings that the plugin generates and then move on to the next one?

Sounds about right to me. We'd basically need to install them as additional_dependencies (see https://pre-commit.com/#pre-commit-configyaml---hooks) to the flake8 pre-commit task.

Okay, so I added all flake8-plugins mentioned above in pre-commit task with their latest versions

$ git diff
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0a7847c1..689f1f3a 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -20,6 +20,19 @@ repos:
   rev: 3.8.1
   hooks:
   - id: flake8
+    additional_dependencies: [
+      'flake8-bugbear==20.1.4',
+      'flake8-coding==1.3.2',
+      'flake8-comprehensions==3.2.2',
+      'flake8-debugger==3.2.1',
+      'flake8-deprecated==1.3',
+      'flake8-docstrings==1.5.0',
+      'flake8-pep3101==1.3.0',
+      'flake8-polyfill==1.0.2',
+      'flake8-print==3.1.4',
+      'flake8-quotes==3.2.0',
+      'flake8-string-format==0.3.0',
+    ]
     exclude: tests/data

but then on running tox -e lint, flake8 didn't complain at all about potential warnings which could be found by the added plugins, which I found surprising.

Is flake8 actually picking the dependencies, or is there anything else we need to add here?

@pfmoore
Copy link
Member

pfmoore commented Jun 4, 2020

Do you need to rebuild the env with tox -re? You could try adding a deliberate violation, and see if the lint run flags it...

@deveshks
Copy link
Contributor

deveshks commented Jun 4, 2020

Do you need to rebuild the env with tox -re? You could try adding a deliberate violation, and see if the lint run flags it...

Okay, doing tox -re lint and adding a deliberate violation by using a bare except: was flagged, but it was flagged as E722 do not use bare 'except', and not B001: Do not use bare except: from flake8-bugbear

I also see that some of them have a list of rules, e.g. flake8-comprehensions, and the violations only get picked when we specify a certain rule via flake8 --select.

For e.g. if we have something like this for that plugin

x = tuple()

Running flake8 script.py doesn't do anything, but doing flake8 --select C408 script.py warns with C408 Unnecessary tuple call - rewrite as a literal.

So it might be the case that most of the plugins need to have the rules to apply explicitly declared?

@deveshks
Copy link
Contributor

deveshks commented Jun 4, 2020

As an example, adding args: ['--select=C4'] to the flake8 hook in pre-commit where C4 prefix should cover all list comprehension rules in flake8-comprehensions, and running tox -e lint uncovered these.

tests/unit/test_req_uninstall.py:165:21: C405 Unnecessary list literal - rewrite as a set literal.
tests/unit/test_req_uninstall.py:167:21: C405 Unnecessary list literal - rewrite as a set literal.
src/pip/_internal/operations/prepare.py:179:14: C408 Unnecessary dict call - rewrite as a literal.
tests/unit/test_vcs.py:64:9: C408 Unnecessary dict call - rewrite as a literal.
tests/lib/__init__.py:699:26: C407 Unnecessary list comprehension - 'any' can take a generator.
tests/lib/__init__.py:701:24: C407 Unnecessary list comprehension - 'any' can take a generator.
tests/lib/__init__.py:708:12: C408 Unnecessary dict call - rewrite as a literal.
tests/unit/test_finder.py:253:20: C414 Unnecessary reversed call within sorted().
tests/unit/test_finder.py:279:20: C414 Unnecessary reversed call within sorted().
src/pip/_internal/resolution/resolvelib/candidates.py:55:17: C408 Unnecessary dict call - rewrite as a literal.
src/pip/_internal/resolution/resolvelib/candidates.py:76:17: C408 Unnecessary dict call - rewrite as a literal.
src/pip/_internal/resolution/resolvelib/candidates.py:99:17: C408 Unnecessary dict call - rewrite as a literal.
src/pip/_internal/locations.py:137:23: C408 Unnecessary dict call - rewrite as a literal.
tests/functional/test_new_resolver.py:17:17: C401 Unnecessary generator - rewrite as a set comprehension.
tests/functional/test_new_resolver.py:21:16: C401 Unnecessary generator - rewrite as a set comprehension.
tests/functional/test_new_resolver.py:28:17: C401 Unnecessary generator - rewrite as a set comprehension.
tests/functional/test_new_resolver.py:34:16: C401 Unnecessary generator - rewrite as a set comprehension.
tests/functional/test_new_resolver.py:43:17: C401 Unnecessary generator - rewrite as a set comprehension.
src/pip/_internal/commands/show.py:71:15: C407 Unnecessary list comprehension - 'sorted' can take a generator.
src/pip/_internal/commands/show.py:82:16: C412 Unnecessary list comprehension - 'in' can take a generator.
src/pip/_internal/utils/wheel.py:124:20: C401 Unnecessary generator - rewrite as a set comprehension.
tests/unit/test_target_python.py:46:10: C408 Unnecessary dict call - rewrite as a literal.
tests/unit/test_target_python.py:48:13: C408 Unnecessary dict call - rewrite as a literal.
tests/unit/test_target_python.py:52:13: C408 Unnecessary dict call - rewrite as a literal.
tests/unit/test_logging.py:57:17: C408 Unnecessary dict call - rewrite as a literal.
src/pip/_internal/commands/freeze.py:89:25: C408 Unnecessary dict call - rewrite as a literal.
src/pip/_internal/req/req_uninstall.py:125:16: C402 Unnecessary generator - rewrite as a dict comprehension.
src/pip/_internal/req/req_uninstall.py:127:24: C401 Unnecessary generator - rewrite as a set comprehension.
src/pip/_internal/req/req_uninstall.py:465:12: C412 Unnecessary set comprehension - 'in' can take a generator.
tests/unit/test_wheel.py:99:9: C406 Unnecessary list literal - rewrite as a dict literal.
src/pip/_internal/commands/search.py:118:29: C407 Unnecessary list comprehension - 'max' can take a generator.
tests/functional/test_list.py:205:12: C408 Unnecessary dict call - rewrite as a literal.
tests/functional/test_list.py:208:12: C408 Unnecessary dict call - rewrite as a literal.
tests/functional/test_search.py:200:20: C407 Unnecessary list comprehension - 'sorted' can take a generator.

I can take this exercise as an example, and start adding flake8-comprehensions plugin by this method if it looks good.

We can also use --enable-extensions=C4 for the same, which will not cause us to lose the default --select values.

@deveshks
Copy link
Contributor

deveshks commented Jun 4, 2020

Hi @asottile

Sorry for pinging you here, but I wanted to get your input as well on how to proceed with this task, especially in light of the last 4-5 comments in this issue.

@asottile
Copy link
Contributor

asottile commented Jun 4, 2020

the reason plugins are not being reported is from this line (which can just be removed, that's the default setting)

I'd also suggest changing the line after it to extend-ignore = W504 -- though W504 is ignored by default so it's likely that line can be removed entirely as well

@deveshks
Copy link
Contributor

deveshks commented Jun 4, 2020

the reason plugins are not being reported is from this line (which can just be removed, that's the default setting)

I'd also suggest changing the line after it to extend-ignore = W504 -- though W504 is ignored by default so it's likely that line can be removed entirely as well

Thanks for that pointer. I got rid of that line, and the pre-commit check of flake-8 outputted 9910 lines, with all plugins listed in #5537 (comment) added to the .pre-commit-config.yaml

tox output.txt

Also if I am planning to add these plugins one-by one, is there any specific ones which you suggest I should start with (given you have had some experience with them in the past).

Also what steps should ideally be followed for adding these plugins? Are there a list of best-practices somewhere. From what I can think of right now is to add the plugin in additional_dependencies, and start by fixing all warnings raised by it if it's not too large a change, otherwise pick the rules most suited after discussion, and only apply those, perhaps via --enable-extensions.

(I can also add more options to select and ignore options to have tighter control on the allowed rules we add by these plugins, something like what flask does)

@asottile
Copy link
Contributor

asottile commented Jun 4, 2020

fwiw flake8-coding can be replaced with fix-encoding-pragma and depending on what quote type you want double-quote-string-fixer can help there too

(this isn't my project, but) -- I would suggest starting with the plugins which are the least controversial and/or with the lowest number of violations and add them one by one (probably deciding on a per-plugin basis which ones to keep or not)

it looks to me like almost all of the violations in your report above come from flake8-quotes and flake8-docstrings (and I'm fairly certain you don't want to sign up for adding docstrings in a few thousand places!)

@deveshks
Copy link
Contributor

deveshks commented Jun 4, 2020

Thanks for all your inputs. I think starting from the least controversial and with lowest violations make sense, since it should have a smaller footprint of changes, and can serve as a baseline on adding future plugins in terms of process being followed.

Given these inputs, I would probably start with either flake8-bugbear and flake8-comprehensions in that order, since those seems to be geared more towards correct coding practices, especially flake8-bugbear,

(The footprint of flake8-comprehensions is already shown at #5537 (comment) , and for flake8-bugbear, it is as follows:)

src/pip/_internal/vcs/subversion.py:59:25: B007 Loop control variable 'files' not used within the loop body. If this is intended, start the name with an underscore.
tests/unit/test_req_file.py:431:13: B011 Do not call assert False since python -O removes these calls. Instead callers should raise AssertionError().
tests/unit/test_req_file.py:500:13: B011 Do not call assert False since python -O removes these calls. Instead callers should raise AssertionError().
tests/unit/test_req_file.py:564:13: B007 Loop control variable 'req' not used within the loop body. If this is intended, start the name with an underscore.
tests/functional/test_vcs_git.py:44:9: B007 Loop control variable 'index' not used within the loop body. If this is intended, start the name with an underscore.
tests/lib/__init__.py:267:68: B006 Do not use mutable data structures for argument defaults.  They are created during function definition time. All calls to the function reuse this one instance of that data structure, persisting changes between them.
tests/lib/__init__.py:268:40: B006 Do not use mutable data structures for argument defaults.  They are created during function definition time. All calls to the function reuse this one instance of that data structure, persisting changes between them.
tests/lib/__init__.py:938:9: B011 Do not call assert False since python -O removes these calls. Instead callers should raise AssertionError().
src/pip/_internal/utils/filesystem.py:68:5: B014 Redundant exception types in `except (OSError, IOError):`.  Write `except ():`, which catches exactly the same exceptions.
src/pip/_internal/utils/filesystem.py:158:9: B007 Loop control variable 'i' not used within the loop body. If this is intended, start the name with an underscore.
src/pip/_internal/utils/filesystem.py:193:15: B007 Loop control variable 'dirs' not used within the loop body. If this is intended, start the name with an underscore.
src/pip/_internal/utils/misc.py:145:5: B014 Redundant exception types in `except (IOError, OSError):`.  Write `except ():`, which catches exactly the same exceptions.
src/pip/_internal/utils/misc.py:551:24: B305 `.next()` is not a thing on Python 3. Use the `next()` builtin. For Python 2 compatibility, use `six.next()`.
src/pip/_internal/utils/misc.py:615:24: B009 Do not call getattr with a constant attribute value, it is not any safer than normal property access.
tests/unit/test_network_auth.py:181:13: B011 Do not call assert False since python -O removes these calls. Instead callers should raise AssertionError().
tests/unit/test_network_auth.py:221:9: B011 Do not call assert False since python -O removes these calls. Instead callers should raise AssertionError().
tests/unit/test_configuration.py:181:13: B011 Do not call assert False since python -O removes these calls. Instead callers should raise AssertionError().
tests/functional/test_install.py:1361:9: B007 Loop control variable 'top' not used within the loop body. If this is intended, start the name with an underscore.
tests/functional/test_install.py:1361:14: B007 Loop control variable 'dirs' not used within the loop body. If this is intended, start the name with an underscore.
tests/functional/test_pep517.py:9:35: B006 Do not use mutable data structures for argument defaults.  They are created during function definition time. All calls to the function reuse this one instance of that data structure, persisting changes between them.
src/pip/_internal/network/cache.py:32:5: B014 Redundant exception types in `except (OSError, IOError):`.  Write `except ():`, which catches exactly the same exceptions.
src/pip/_internal/wheel_builder.py:39:25: B008 Do not perform function calls in argument defaults.  The call is performed only once at function definition time. All calls to your function will reuse the result of that definition-time function call.  If this is intended, assign the function call to a module-level variable and use that variable as a default value.
tests/yaml/linter.py:71:9: B007 Loop control variable 'i' not used within the loop body. If this is intended, start the name with an underscore.
src/pip/_internal/commands/debug.py:90:30: B009 Do not call getattr with a constant attribute value, it is not any safer than normal property access.
src/pip/_internal/commands/debug.py:169:14: B007 Loop control variable 'value' not used within the loop body. If this is intended, start the name with an underscore.
tests/unit/resolution_resolvelib/test_requirement.py:53:21: B007 Loop control variable 'matches' not used within the loop body. If this is intended, start the name with an underscore.
tests/unit/resolution_resolvelib/test_requirement.py:60:15: B007 Loop control variable 'name' not used within the loop body. If this is intended, start the name with an underscore.
tests/unit/resolution_resolvelib/test_requirement.py:69:15: B007 Loop control variable 'name' not used within the loop body. If this is intended, start the name with an underscore.
tests/unit/resolution_resolvelib/test_requirement.py:69:21: B007 Loop control variable 'matches' not used within the loop body. If this is intended, start the name with an underscore.
tests/unit/test_utils_wheel.py:24:26: B007 Loop control variable 'dirnames' not used within the loop body. If this is intended, start the name with an underscore.
src/pip/_internal/cli/progress_bars.py:170:13: B305 `.next()` is not a thing on Python 3. Use the `next()` builtin. For Python 2 compatibility, use `six.next()`.

Hi Pradyun, Paul,

Let me know if those two plugins are a good starting point for this exercise, and they are not too intrusive in the ongoing PRs (requiring a major rebase effort after a PR merge)

@asottile
Copy link
Contributor

asottile commented Jun 4, 2020

you'll want to per-file-ignores = tests*: B101 probably as well

@deveshks
Copy link
Contributor

deveshks commented Jun 4, 2020

you'll want to per-file-ignores = tests*: B101 probably as well

Are you talking about https://docs.openstack.org/bandit/latest/plugins/b101_assert_used.html in the above statement? (I couldn't find a B101 rule in the docs of the listed plugins)

@McSinyx
Copy link
Contributor

McSinyx commented Jun 10, 2020

That would solve GH-7009, which I am still interested in taken care of.

@deveshks
Copy link
Contributor

deveshks commented Jun 10, 2020

Could you describe what it does? Specifically, what sort of code would it block?

From what I could gather from the repo, flake8-comprehensions would look at the existing list/set/dict comprehensions in pip's codebase, flag unnecessary constructs used in them and suggest simpler equivalent of those according to the rules defined in the readme.

Some examples of existing code in pip, and flake8-comprehension applied on it are as follows.

log_messages = sorted([r.getMessage() for r in caplog.records])

complains with C407 Unnecessary list comprehension - 'sorted' can take a generator. and can be fixed with

log_messages = sorted(r.getMessage() for r in caplog.records)

subdirs = list(set(p.split("/")[0] for p in source.namelist()))

complains with C401 Unnecessary generator - rewrite as a set comprehension., and can be rewritten as

subdirs = list({p.split("/")[0] for p in source.namelist()})

case_map = dict((os.path.normcase(p), p) for p in paths)
remaining = set(case_map)
unchecked = sorted(set(os.path.split(p)[0]
for p in case_map.values()), key=len)

complains with

src/pip/_internal/req/req_uninstall.py:125:16: C402 Unnecessary generator - rewrite as a dict comprehension.
src/pip/_internal/req/req_uninstall.py:127:24: C401 Unnecessary generator - rewrite as a set comprehension.

and can be rewritten as

case_map = {os.path.normcase(p): p for p in paths}
remaining = set(case_map)
unchecked = sorted({os.path.split(p)[0]
                        for p in case_map.values()}, key=len)

These are just some of the refactorings suggested by the plugin.

Also, does it reject any existing code in pip?

Yes, it currently flags some existing code in pip. The full set of violations for the plugin are as follows:

tests/unit/test_req_uninstall.py:165:21: C405 Unnecessary list literal - rewrite as a set literal.
tests/unit/test_req_uninstall.py:167:21: C405 Unnecessary list literal - rewrite as a set literal.
tests/lib/__init__.py:701:26: C407 Unnecessary list comprehension - 'any' can take a generator.
tests/lib/__init__.py:703:24: C407 Unnecessary list comprehension - 'any' can take a generator.
tests/unit/test_finder.py:253:20: C414 Unnecessary reversed call within sorted().
tests/unit/test_finder.py:279:20: C414 Unnecessary reversed call within sorted().
tests/functional/test_new_resolver.py:17:17: C401 Unnecessary generator - rewrite as a set comprehension.
tests/functional/test_new_resolver.py:21:16: C401 Unnecessary generator - rewrite as a set comprehension.
tests/functional/test_new_resolver.py:28:17: C401 Unnecessary generator - rewrite as a set comprehension.
tests/functional/test_new_resolver.py:34:16: C401 Unnecessary generator - rewrite as a set comprehension.
tests/functional/test_new_resolver.py:43:17: C401 Unnecessary generator - rewrite as a set comprehension.
src/pip/_internal/commands/show.py:71:15: C407 Unnecessary list comprehension - 'sorted' can take a generator.
src/pip/_internal/commands/show.py:82:16: C412 Unnecessary list comprehension - 'in' can take a generator.
src/pip/_internal/utils/wheel.py:124:20: C401 Unnecessary generator - rewrite as a set comprehension.
src/pip/_internal/req/req_uninstall.py:125:16: C402 Unnecessary generator - rewrite as a dict comprehension.
src/pip/_internal/req/req_uninstall.py:127:24: C401 Unnecessary generator - rewrite as a set comprehension.
src/pip/_internal/req/req_uninstall.py:465:12: C412 Unnecessary set comprehension - 'in' can take a generator.
tests/unit/test_wheel.py:99:9: C406 Unnecessary list literal - rewrite as a dict literal.
src/pip/_internal/commands/search.py:118:29: C407 Unnecessary list comprehension - 'max' can take a generator.
tests/functional/test_search.py:200:20: C407 Unnecessary list comprehension - 'sorted' can take a generator.

@deveshks
Copy link
Contributor

I think flake8-logging-format would be a good one.

I will look into adding this as well, but should I add this before flake8-comprehensions, or after it?

@deveshks
Copy link
Contributor

deveshks commented Jun 10, 2020

That would solve GH-7009, which I am still interested in taken care of.

That's great to hear and adding a plugin will allow us to move forward in some existing issues.

Although on second look, that issue is for regular strings, whereas flake8-logging-format will catch violations only for strings used in logger.* calls.

@McSinyx
Copy link
Contributor

McSinyx commented Jun 10, 2020

There are only (non-progressbar, non-logging) % formatting that I am aware of

logger.error('Cannot build wheel for %s using PEP 517 when '
'--build-option is present' % (name,))

The rest will be pretty much mindless OCD-favored reformatting which I adore 😄

@deveshks
Copy link
Contributor

The rest will be pretty much mindless OCD-favored reformatting which I adore 😄

Adding to that, here are the list of current violations caught by flake8-logging-format.

src/pip/_internal/utils/direct_url_helpers.py:124:9: G200 Logging statement uses exception in arguments
src/pip/_internal/operations/prepare.py:149:9: G200 Logging statement uses exception in arguments
src/pip/_internal/operations/prepare.py:477:17: G200 Logging statement uses exception in arguments
src/pip/_internal/commands/install.py:437:13: G201 Logging: .exception(...) should be used instead of .error(..., exc_info=True)
src/pip/_internal/commands/install.py:508:13: G201 Logging: .exception(...) should be used instead of .error(..., exc_info=True)
src/pip/_internal/operations/check.py:56:13: G200 Logging statement uses exception in arguments
src/pip/_internal/cli/cmdoptions.py:850:22: G001 Logging statement uses string.format()
src/pip/_internal/cli/cmdoptions.py:854:22: G001 Logging statement uses string.format()
src/pip/_internal/operations/install/wheel.py:296:17: G001 Logging statement uses string.format()
src/pip/_internal/commands/cache.py:62:26: G001 Logging statement uses string.format()
src/pip/_internal/resolution/resolvelib/candidates.py:205:13: G200 Logging statement uses exception in arguments
src/pip/_internal/utils/temp_dir.py:188:22: G001 Logging statement uses string.format()
src/pip/_internal/utils/temp_dir.py:273:22: G001 Logging statement uses string.format()
src/pip/_internal/utils/subprocess.py:194:13: G200 Logging statement uses exception in arguments
src/pip/_internal/utils/subprocess.py:244:17: G001 Logging statement uses string.format()
src/pip/_internal/network/auth.py:38:5: G200 Logging statement uses exception in arguments
src/pip/_internal/network/auth.py:69:9: G200 Logging statement uses exception in arguments
src/pip/_internal/models/search_scope.py:98:25: G001 Logging statement uses string.format()
src/pip/_internal/wheel_builder.py:180:9: G200 Logging statement uses exception in arguments
src/pip/_internal/wheel_builder.py:232:17: G200 Logging statement uses exception in arguments
src/pip/_internal/index/collector.py:528:25: G001 Logging statement uses string.format()
src/pip/_internal/operations/build/wheel.py:29:22: G002 Logging statement uses '%'
src/pip/_internal/cli/base_command.py:198:13: G200 Logging statement uses exception in arguments
src/pip/_internal/cli/base_command.py:204:13: G200 Logging statement uses exception in arguments
src/pip/_internal/cli/base_command.py:209:13: G200 Logging statement uses exception in arguments
src/pip/_internal/req/constructors.py:175:26: G001 Logging statement uses string.format()
src/pip/_internal/utils/unpacking.py:209:21: G200 Logging statement uses exception in arguments
src/pip/_internal/utils/unpacking.py:220:21: G200 Logging statement uses exception in arguments
src/pip/_internal/req/req_uninstall.py:310:17: G200 Logging statement uses exception in arguments
src/pip/_internal/req/req_uninstall.py:615:17: G001 Logging statement uses string.format()
src/pip/_internal/commands/configuration.py:111:26: G001 Logging statement uses string.format()
src/pip/_internal/commands/configuration.py:229:13: G201 Logging: .exception(...) should be used instead of .error(..., exc_info=True)
src/pip/_internal/commands/debug.py:32:17: G001 Logging statement uses string.format()
src/pip/_internal/commands/debug.py:120:13: G001 Logging statement uses string.format()
src/pip/_internal/resolution/legacy/resolver.py:83:9: G200 Logging statement uses exception in arguments
src/pip/_internal/vcs/versioncontrol.py:125:13: G200 Logging statement uses exception in arguments
noxfile.py:273:13: G004 Logging statement uses f-string
noxfile.py:284:13: G004 Logging statement uses f-string
src/pip/_internal/commands/wheel.py:178:17: G200 Logging statement uses exception in arguments
src/pip/_internal/vcs/mercurial.py:78:13: G200 Logging statement uses exception in arguments
src/pip/_internal/resolution/resolvelib/resolver.py:152:25: G003 Logging statement uses '+'
src/pip/_internal/resolution/resolvelib/resolver.py:152:25: G003 Logging statement uses '+'
src/pip/_internal/resolution/resolvelib/resolver.py:152:25: G003 Logging statement uses '+'
src/pip/_internal/resolution/resolvelib/resolver.py:155:52: G001 Logging statement uses string.format()
src/pip/_internal/operations/freeze.py:74:13: G200 Logging statement uses exception in arguments
src/pip/_internal/operations/freeze.py:222:9: G200 Logging statement uses exception in arguments
src/pip/_internal/cli/main.py:72:9: G200 Logging statement uses exception in arguments
src/pip/_internal/cache.py:233:21: G001 Logging statement uses string.format()

@McSinyx
Copy link
Contributor

McSinyx commented Jun 10, 2020

I think I understood flake8-logging-format completely wrong and I don't understand why we'd want to use extra to

check that strings aren't formatted while passing to logging (should depend on logging doing the interpolation)

extra is meant for LogRecord, not for the message content. What we really want is either loguru (it's really nice, snake_case, colors and stuff, but py35+) or to use logging.LoggerAdapter, but the later is only to make the interface more pretty, the interpolation is still not done by pip.

Furthermore, what's the benefit of letting logging doing the formatting? I agree that call('{} {foo} {bar}', 1, foo=2, bar=3) is prettier than call('{} {foo} {bar}'.format(1, foo=2, bar=3)) (let's ignore the existence of the % formatting), but I fail to see gains from security standpoint as described in the plugin's README.

@asottile
Copy link
Contributor

The reason for that (I don't necessarily agree in all cases since it's been shown that eagerly formatting f-strings is actually faster in most cases) is that you incur unnecessary overhead for logging statements that may be entirely discarded (performance). Avoiding the format and letting the logging machinery do it only when necessary saves a few cycles.

# good
logging.debug('foo %s %s', thing1, thing2)
# bad
logging.debug(f'foo {thing1} {thing2}')

@McSinyx
Copy link
Contributor

McSinyx commented Jun 10, 2020

Thanks, @asottile. So do you think it'd be reasonable to go with LoggerAdapter for lazy interpolation and forget about the logging-format extension?

@deveshks
Copy link
Contributor

I think flake8-logging-format also ensures other good practices apart from just being about using %s and letting logging.* function calls doing the formatting, like enforcing the non-usage of deprecated logger.warn and usage of logger.exception etc.

But I agree that if we use LoggerAdapter for all our logging format needs, we won't be needing that, but that would be a bigger task to take up then using flake8-logging-format.

@McSinyx
Copy link
Contributor

McSinyx commented Jun 10, 2020

I've just filed GH-8423 for some of these additional good practices. I'm not sure if we want to opt-in any other before wrapping LoggerAdapter (I'm not sure if this plugin is able to analyze the logger after we wrap it).

@deveshks
Copy link
Contributor

deveshks commented Jun 10, 2020

Hi @pfmoore , @pradyunsg

Given my understanding of flake8-comprehensions in #5537 (comment), I wanted to get your opinion on whether it's worthwhile to add that plugin to pip? Given that @McSinyx has picked up flake8-logging-format to be added to pip in parallel.

@McSinyx
Copy link
Contributor

McSinyx commented Jun 12, 2020

I wonder if we want to add flake8-builtins to the pending list of plugins to try.

@deveshks
Copy link
Contributor

I wonder if we want to add flake8-builtins to the pending list of plugins to try.

That's a good idea too. I will probably give it a shot in a PR once I get some clarity on whether we want to use flake8-comprehensions or not.

@webknjaz
Copy link
Member

@deveshks @McSinyx how about adding some plugins from the list here https://wemake-python-stylegui.de/en/latest/pages/usage/violations/index.html? Plus maybe https://github.com/m-burst/flake8-pytest-style

@deveshks
Copy link
Contributor

That's a good list for reference. I also see that most of them are listed in the comments here as well.

But we would need to get agreement on which of these are most useful from the maintainers, as well as in what order should we be adding them (probably from least invasive to the most).

@webknjaz
Copy link
Member

Right. Also, FTR wemake-python-styleguide is developing a baseline support that would be very helpful in terms of invasiveness. Here's also a few words on supporting "legacy" codebases that you may find interesting: https://wemake-python-stylegui.de/en/latest/pages/usage/integrations/legacy.html.

@deveshks
Copy link
Contributor

Here's also a few words on supporting "legacy" codebases that you may find interesting: https://wemake-python-stylegui.de/en/latest/pages/usage/integrations/legacy.html.

Thanks for that. I will keep this in mind when integrating more plugins into pip as we move forward.

@deveshks
Copy link
Contributor

deveshks commented Jul 19, 2020

Given that flake8-logging-format is now integrated with pip, can I go ahead and pick flake8-comprehensions to be integrated with pip next?

@pradyunsg
Copy link
Member Author

If someone wants to figure out what the next steps here are, that'd be great!

@deveshks
Copy link
Contributor

deveshks commented Dec 2, 2020

HI @pradyunsg ,

As per my earlier comment, I was thinking of integrating flake8-comprehensions. Let me know if that's a good idea and I will create a PR accordingly.

@ichard26
Copy link
Member

We don't need this issue around anymore as flake8 got replaced by ruff here: #12073.

@webknjaz
Copy link
Member

Does Ruff cover all the plugins discussed? If not, it could be reasonable to run flake8 against a small subset of rules.

@ichard26
Copy link
Member

As far as I understood the Ruff PR, a big point was to take advantage of Ruff's speed and the fact it's just one tool. Bringing back just a few flake8 plugins would defeat the purpose of using Ruff.

@notatallshaw
Copy link
Contributor

ruff has many of the rules discussed and they are often adding more: https://docs.astral.sh/ruff/rules/#flake8-2020-ytt

I'm sure someone sufficiently motivated could add more rules discussed here to pip's configuration that was discussed here.

@pradyunsg
Copy link
Member Author

I just went through and checked...

flake8 plugin Supported by ruff? notes
flake8-bugbear 🟢 https://docs.astral.sh/ruff/rules/#flake8-bugbear-b
flake8-coding 🔴 Does it matter? No. We are Python 3+ which is UTF-8 by default.
flake8-comprehensions 🟢 https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4
flake8-debugger 🟢 https://docs.astral.sh/ruff/rules/#flake8-debugger-t10
flake8-deprecated 🔴 Does it matter? No. The implementation has nothing that is useful to us AFAICT.
flake8-docstrings 🟢 It directly supports https://docs.astral.sh/ruff/rules/#pydocstyle-d.
flake8-pep3101 🔴 Does it matter? No. I don't think it's useful to enforce this, and we were generally lukewarm about it as well.
flake8-polyfill 🔴 Does it matter? No. It's for flake8 compatibility shims anyway.
flake8-print 🟢 https://docs.astral.sh/ruff/rules/#flake8-print-t20
flake8-quotes 🟢 https://docs.astral.sh/ruff/rules/#flake8-quotes-q
flake8-string-format 🟠 Does it matter? Sort-of. These are covered by https://docs.astral.sh/ruff/rules/#pyflakes-f as far as I can tell.

I think we'll be OK.

Internal Cleansing automation moved this from To do to Done Apr 16, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 17, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
help wanted For requesting inputs from other members of the community type: maintenance Related to Development and Maintenance Processes
Projects
Development

No branches or pull requests

9 participants