:pizza: A flake8 extension that implements misc. lints

sbdchd, updated 🕥 2023-02-21 21:33:40

flake8-pie CircleCI pypi

A flake8 extension that implements misc. lints

DEPRECATED: See Ruff's flake8-pie implementation instead

lints

PIE781: assign-and-return

Based on Clippy's let_and_return and Microsoft's TSLint rule no-unnecessary-local-variable.

For more info on the structure of this lint, see the accompanying blog post.

examples

```python

error

def foo(): x = bar() return x

allowed

def foo(): x, _ = bar() return x ```

PIE783: celery-explicit-names

Warn about Celery task definitions that don't have explicit names.

Note: this lint is kind of naive considering any decorator with a .task() method or any decorator called shared_task() a Celery decorator.

examples

```python

error

@app.task() def foo(): pass

ok

@app.task(name="app_name.tasks.foo") def foo(): pass ```

PIE784: celery-explicit-crontab-args

The crontab class provided by Celery has some default args that are suprising to new users. Specifically, crontab(hour="0,12") won't run a task at midnight and noon, it will run the task at every minute during those two hours. This lint makes that call an error, forcing you to write crontab(hour="0, 12", minute="*").

Additionally, the lint is a bit more complex in that it requires you specify every smaller increment than the largest time increment you provide. So if you provide days_of_week, then you need to provide hours and minutes explicitly.

Note: if you like the default behavior of crontab() then you can either disable this lint or pass "*" for the kwarg value, e.g., minutes="*".

Also, since this lint is essentially a naive search for calls to a crontab() function, if you have a function named the same then this will cause false positives.

PIE785: celery-require-tasks-expire

Celery tasks can bunch up if they don't have expirations.

This enforces specifying expirations in both the celery beat config dict and in .apply_async() calls.

The same caveat applies about how this lint is naive.

PIE786: precise-exception-handlers

Be precise in what exceptions you catch. Bare except: handlers, catching BaseException, or catching Exception can lead to unexpected bugs.

examples

```python

error

try: save_file(name="export.csv") except: pass

error

try: save_file(name="export.csv") except BaseException: pass

error

try: save_file(name="export.csv") except Exception: pass

error

try: save_file(name="export.csv") except (ValueError, Exception): pass

ok

try: save_file(name="export.csv") except OSError: pass ```

PIE787: no-len-condition

Empty collections are falsey in Python so calling len() is unnecessary when checking for emptiness in an if statement/expression.

Comparing to explicit scalars is allowed.

```python

error

if len(foo): ... if not len(foo): ...

ok

if foo: ... if not foo: ... if len(foo) > 0: ... if len(foo) == 0: ... ```

PIE788: no-bool-condition

If statements/expressions evalute the truthiness of the their test argument, so calling bool() is unnecessary.

Comparing to True/False is allowed.

```python

error

if bool(foo): ... if not bool(foo): ...

ok

if foo: ... if not foo: ... if bool(foo) is True: ... if bool(foo) is False: ... ```

PIE789: prefer-isinstance-type-compare

Using type() doesn't take into account subclassess and type checkers won't refine the type, use isinstance instead.

```python

error

if type(foo) == str: ... if type(foo) is str: ... if type(foo) in [int, str]: ...

ok

if isinstance(foo, str): ... if isinstance(foo, (int, str)): ... ```

PIE790: no-unnecessary-pass

pass is unnecessary when definining a class or function with an empty body.

```python

error

class BadError(Exception): """ some doc comment """ pass

def foo() -> None: """ some function """ pass

ok

class BadError(Exception): """ some doc comment """

def foo() -> None: """ some function """ ```

PIE791: no-pointless-statements

Comparisions without an assignment or assertion are probably a typo.

```python

error

"foobar" in data res.json() == [] user.is_authenticated() is True

ok

assert "foobar" in data foo = res.json() == [] use.is_authenticated() ```

PIE792: no-inherit-object

Inheriting from object isn't necessary in Python 3.

```python

error

class Foo(object): ...

ok

class Foo: ... ```

PIE793: prefer-dataclass

Attempts to find cases where the @dataclass decorator is unintentionally missing.

```python

error

class Foo: z: dict[int, int] def init(self) -> None: ...

class Bar: x: list[str]

ok

class Bar(Foo): z: dict[int, int]

@dataclass class Bar: x: list[str] ```

PIE794: dupe-class-field-definitions

Finds duplicate definitions for the same field, which can occur in large ORM model definitions.

```python

error

class User(BaseModel): email = fields.EmailField() # ...80 more properties... email = fields.EmailField()

ok

class User(BaseModel): email = fields.EmailField() # ...80 more properties... ```

PIE795: prefer-stdlib-enums

Instead of defining various constant properties on a class, use the stdlib enum which typecheckers support for type refinement.

```python

error

class Foo: A = "A" B = "B" C = "C"

ok

import enum class Foo(enum.Enum): A = "A" B = "B" C = "C" ```

PIE796: prefer-unique-enums

By default the stdlib enum allows multiple field names to map to the same value, this lint requires each enum value be unique.

```python

error

class Foo(enum.Enum): A = "A" B = "B" C = "C" D = "C"

ok

class Foo(enum.Enum): A = "A" B = "B" C = "C" D = "D" ```

PIE797: no-unnecessary-if-expr

Call bool() directly rather than reimplementing its functionality.

```python

error

foo(is_valid=True if buzz() else False)

ok

foo(is_valid=bool(buzz())) ```

PIE798: no-unnecessary-class

Instead of using class to namespace functions, use a module.

```python

error

class UserManager: class User(NamedTuple): name: str

@classmethod
def update_user(cls, user: User) -> None:
    ...

@staticmethod
def sync_users() -> None:
    ...

ok

class User(NamedTuple): name: str

def update_user(user: User) -> None: ...

def sync_users() -> None: ... ```

PIE799: prefer-col-init

Check that values are passed in when collections are created rather than creating an empty collection and then inserting.

```python

error

bars = [] bar = bar() bars.append(bar)

ok

bar = bar() bars = [bar]

error

s = deque() s.append(foo)

ok

s = deque([foo]) ```

PIE800: no-unnecessary-spread

Check for unnecessary dict unpacking.

```python

error

{foo, {"bar": 10}}

ok

{**foo, "bar": 10} ```

PIE801: prefer-simple-return

Return boolean expressions directly instead of returning True and False.

```python

error

def main(): if foo > 5: return True return False

error

def main(): if foo > 5: return True else: return False

ok

def main(): return foo > 5 ```

PIE802: prefer-simple-any-all

Remove unnecessary comprehensions for any and all

```python

error

any([x.id for x in bar]) all([x.id for x in bar])

ok

all(x.id for x in bar) any(x.id for x in bar) any({x.id for x in bar}) ```

PIE803: prefer-logging-interpolation

Don't format strings before logging. Let logging interpolate arguments.

This allows Sentry to aggregate logs, prevents raising exceptions if interpolation fails, and improves performance if the log level is disabled. See "PyCQA/pylint#1788".

```python

error

logger.info("Login error for %s" % user) logger.info("Login error for %s, %s" % (user_id, name))

error

logger.info("Login error for {}".format(user)) logger.info("Login error for {}, {}".format(user_id, name))

error

logger.info(f"Login error for {user}") logger.info(f"Login error for {user_id}, {name}")

ok

logger.info("Login error for %s", user) logger.info("Login error for %s, %s", user_id, name) ```

PIE804: no-unnecessary-dict-kwargs

As long as the keys of the dict are valid Python identifier names, we can safely remove the surrounding dict.

```python

error

foo(**{"bar": True})

ok

foo(bar=True) foo(buzz) foo({"bar foo": True}) ```

dev

```shell

install dependencies

poetry install

s/lint s/test ```

PIE805: prefer-literal

Currently only checks for byte string literals.

```python

error

"foo".encode()

ok

b"foo" "😀".encode() ```

PIE806: no-assert-except

Instead of asserting and catching the exception, use an if statement.

```python

error

try: assert "@" in bar except AssertionError: ...

ok

if "@" in bar: ... ```

PIE807: prefer-list-builtin

lambda: [] is equivalent to the builtin list

```python

error

@dataclass class Foo: foo: List[str] = field(default_factory=lambda: [])

ok

@dataclass class Foo: foo: List[str] = field(default_factory=list) ```

PIE808: prefer-simple-range

We can leave out the first argument to range in some cases since the default start position is 0.

```python

err

range(0, 10)

ok

range(10) range(x, 10) range(0, 10, x) ```

PIE809: django-prefer-bulk

Bulk create multiple objects instead of executing O(N) queries.

```python

error

[Item.objects.create(item) for item in items]

error

[Item.objects.create(item) for item in [bar for bar in buzz]]

error

(Item.objects.create(item) for item in items)

ok

Item.objects.insert(items) Item.objects.create(item) ```

PIE810: single-starts-ends-with

Instead of calling startswith or endswith on the same string for multiple prefixes, pass the prefixes as a tuple in a single startswith or endswith call.

```python

error

foo.startswith("foo") or foo.startswith("bar")

error

foo.endswith("foo") or foo.endswith("bar")

error

foo.startswith("foo") or str.startswith(foo, "bar")

ok

foo.startswith(("foo", "bar"))

ok

foo.endswith(("foo", "bar"))

ok

foo.startswith("foo") or foo.endswith("bar") ```

development

examining the AST

You can use astpretty to dump the AST of a piece of code.

shell ./.venv/bin/astpretty <(pbpaste)

uploading a new version to PyPi

```shell

increment Flake8PieCheck.version and pyproject.toml version

build new distribution files and upload to pypi

Note: this will ask for login credentials

rm -rf dist && poetry publish --build ```

Issues

build(deps): bump certifi from 2020.12.5 to 2022.12.7

opened on 2022-12-08 08:51:51 by dependabot[bot]

Bumps certifi from 2020.12.5 to 2022.12.7.

Commits


Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) - `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language - `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language - `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language - `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/sbdchd/flake8-pie/network/alerts).

no unnecessary comprehension

opened on 2022-09-04 02:40:48 by sbdchd

py {c for c in " /.0123456789"}

can be

py set(" /.0123456789")

Fix typos

opened on 2022-08-25 15:31:14 by kianmeng

Found via codespell .

Other rules for replacing lambda expressions

opened on 2022-03-27 02:19:53 by ericbn

Since you have "lambda: [] is equivalent to the builtin list", I suggest other rules with similar refactoring strategies:

  • lambda: {} is equivalent to the builtin dict
  • lambda arg: f(arg) is equivalent to f
  • lambda obj: obj.attr is equivalent to operator.attrgetter('attr')
  • lambda items: items[0] is equivalent to operator.itemgetter(0)
  • lambda obj: obj.name() is equivalent to operator.methodcaller('name')

The descriptions are still not generic enough, and maybe should not be the ones to be really used.

Some of these rules can be rather complex, in terms of all the possible variations.

unnecessary-cast

opened on 2021-09-20 16:48:50 by sbdchd

I think this rule could be pretty general but a basic example:

```python

err

for id in list({x.bar for x in foo}): ...

ok

for id in {x.bar for x in foo}: ... for id in list({x.bar for x in foo})[:5]: ... ```

prefer-negated-conditions

opened on 2021-09-08 16:27:13 by sbdchd

```python

err

not foo == bar

ok

foo != bar ```

Releases

v0.15.0 2021-11-06 21:08:22

Better late than never

v0.14.0 2021-06-26 01:40:42

New Rules

  • prefer-simple-range
  • prefer-list-builtin
  • no-assert-except
  • prefer-literal

Update

  • no-unncessary-dict-kwargs to handle unnecessary spread in args

18 new rules since v0.5.0 2021-05-09 20:15:10

New rules from v0.5.0...v0.12.0

  • PIE786: precise-exception-handlers
  • PIE787: no-len-condition
  • PIE788: no-bool-condition
  • PIE789: prefer-isinstance-type-compare
  • PIE790: no-unnecessary-pass
  • PIE791: no-pointless-statements
  • PIE792: no-inherit-object
  • PIE793: prefer-dataclass
  • PIE794: dupe-class-field-definitions
  • PIE795: prefer-stdlib-enums
  • PIE796: prefer-unique-enums
  • PIE797: no-unnecessary-if-expr
  • PIE798: no-unnecessary-class
  • PIE799: prefer-col-init
  • PIE800: no-unnecessary-spread
  • PIE801: prefer-simple-return
  • PIE802: prefer-simple-any-all
  • PIE803: prefer-logging-interpolation

2020-01-08 23:42:56

2019-04-17 03:32:13

2019-04-17 03:06:52

Steve Dignam
GitHub Repository

flake8 lint python