Skip to main content

A flake8 extension that implements misc. lints

Project description

flake8-pie CircleCI pypi

A flake8 extension that implements misc. lints

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

# 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

# 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

# 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 fasley in Python so calling len() is unnecessary when checking for emptiness in an if statement/expression.

Comparing to explicit scalars is allowed.

# 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.

# 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.

# 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.

# 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.

# 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.

# error
class Foo(object):
    ...

# ok
class Foo:
    ...

PIE793: prefer-dataclass

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

# 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.

# 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.

# 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.

# 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.

# 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.

# 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.

# 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.

# error
{**foo, **{"bar": 10}}

# ok
{**foo, "bar": 10}

PIE801: prefer-simple-return

Return boolean expressions directly instead of returning True and False.

# 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

# 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".

# 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.

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

# ok
foo(bar=True)
foo(**buzz)
foo(**{"bar foo": True})

dev

# install dependencies
poetry install

s/lint
s/test

PIE805: prefer-literal

Currently only checks for byte string literals.

# error
"foo".encode()

# ok
b"foo"
"😀".encode()

PIE806: no-assert-except

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

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

# ok
if "@" in bar:
    ...

PIE807: prefer-list-builtin

lambda: [] is equivalent to the builtin list

# 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.

# 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.

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

development

examining the AST

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

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

uploading a new version to PyPi

# 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

flake8-pie-0.16.0.tar.gz (28.3 kB view details)

Uploaded Source

Built Distribution

flake8_pie-0.16.0-py3-none-any.whl (49.2 kB view details)

Uploaded Python 3

File details

Details for the file flake8-pie-0.16.0.tar.gz.

File metadata

  • Download URL: flake8-pie-0.16.0.tar.gz
  • Upload date:
  • Size: 28.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.13 CPython/3.10.5 Darwin/21.5.0

File hashes

Hashes for flake8-pie-0.16.0.tar.gz
Algorithm Hash digest
SHA256 b8dcb7b92706fa33d05d92a4b3e49b7a9fd3f0041849166275b646ba50e515ba
MD5 1e85d8671cbe994dbe0d44422d2667d7
BLAKE2b-256 ae11cebfe54fda9897188ca40d1cd755d1be13908ceedfea9e500fae162f06a9

See more details on using hashes here.

File details

Details for the file flake8_pie-0.16.0-py3-none-any.whl.

File metadata

  • Download URL: flake8_pie-0.16.0-py3-none-any.whl
  • Upload date:
  • Size: 49.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.13 CPython/3.10.5 Darwin/21.5.0

File hashes

Hashes for flake8_pie-0.16.0-py3-none-any.whl
Algorithm Hash digest
SHA256 24cd7849b0eee22e2328b9e9d2a1dea40013b0a3106864bbadd06a4b05dbb71f
MD5 fa13e3baac37f1475229fa84734c715c
BLAKE2b-256 834ce953c2402b663155525faba5315df8abe6c02ea7f989cbf43327f5088fc3

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page