A flake8 extension that implements misc. lints
DEPRECATED: See Ruff's flake8-pie implementation instead
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.
# error
def foo():
x = bar()
return x
# allowed
def foo():
x, _ = bar()
return xWarn 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.
# error
@app.task()
def foo():
pass
# ok
@app.task(name="app_name.tasks.foo")
def foo():
passThe 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.
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.
Be precise in what exceptions you catch. Bare except: handlers, catching BaseException, or catching Exception can lead to unexpected bugs.
# 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:
passEmpty 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.
# error
if len(foo): ...
if not len(foo): ...
# ok
if foo: ...
if not foo: ...
if len(foo) > 0: ...
if len(foo) == 0: ...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: ...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)): ...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
"""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()Inheriting from object isn't necessary in Python 3.
# error
class Foo(object):
...
# ok
class Foo:
...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]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...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"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"Call bool() directly rather than reimplementing its functionality.
# error
foo(is_valid=True if buzz() else False)
# ok
foo(is_valid=bool(buzz()))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:
...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])Check for unnecessary dict unpacking.
# error
{**foo, **{"bar": 10}}
# ok
{**foo, "bar": 10}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 > 5Remove 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})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)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})# install dependencies
poetry install
s/lint
s/testCurrently only checks for byte string literals.
# error
"foo".encode()
# ok
b"foo"
"π".encode()Instead of asserting and catching the exception, use an if statement.
# error
try:
assert "@" in bar
except AssertionError:
...
# ok
if "@" in bar:
...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)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)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)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.
# 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")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