A flake8 extension that implements misc. lints
flake8-pie
implementation insteadBased 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.
```python
def foo(): x = bar() return x
def foo(): x, _ = bar() return x ```
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.
```python
@app.task() def foo(): pass
@app.task(name="app_name.tasks.foo") def foo(): pass ```
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 hour
s and minute
s
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.
```python
try: save_file(name="export.csv") except: pass
try: save_file(name="export.csv") except BaseException: pass
try: save_file(name="export.csv") except Exception: pass
try: save_file(name="export.csv") except (ValueError, Exception): pass
try: save_file(name="export.csv") except OSError: pass ```
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
if len(foo): ... if not len(foo): ...
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.
```python
if bool(foo): ... if not bool(foo): ...
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.
```python
if type(foo) == str: ... if type(foo) is str: ... if type(foo) in [int, str]: ...
if isinstance(foo, str): ... if isinstance(foo, (int, str)): ... ```
pass
is unnecessary when definining a class
or function with an empty
body.
```python
class BadError(Exception): """ some doc comment """ pass
def foo() -> None: """ some function """ pass
class BadError(Exception): """ some doc comment """
def foo() -> None: """ some function """ ```
Comparisions without an assignment or assertion are probably a typo.
```python
"foobar" in data res.json() == [] user.is_authenticated() is True
assert "foobar" in data foo = res.json() == [] use.is_authenticated() ```
Inheriting from object
isn't necessary in Python 3.
```python
class Foo(object): ...
class Foo: ... ```
Attempts to find cases where the @dataclass
decorator is unintentionally
missing.
```python
class Foo: z: dict[int, int] def init(self) -> None: ...
class Bar: x: list[str]
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.
```python
class User(BaseModel): email = fields.EmailField() # ...80 more properties... email = fields.EmailField()
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.
```python
class Foo: A = "A" B = "B" C = "C"
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.
```python
class Foo(enum.Enum): A = "A" B = "B" C = "C" D = "C"
class Foo(enum.Enum): A = "A" B = "B" C = "C" D = "D" ```
Call bool()
directly rather than reimplementing its functionality.
```python
foo(is_valid=True if buzz() else False)
foo(is_valid=bool(buzz())) ```
Instead of using class to namespace functions, use a module.
```python
class UserManager: class User(NamedTuple): name: str
@classmethod
def update_user(cls, user: User) -> None:
...
@staticmethod
def sync_users() -> None:
...
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.
```python
bars = [] bar = bar() bars.append(bar)
bar = bar() bars = [bar]
s = deque() s.append(foo)
s = deque([foo]) ```
Check for unnecessary dict unpacking.
```python
{foo, {"bar": 10}}
{**foo, "bar": 10} ```
Return boolean expressions directly instead of returning True
and False
.
```python
def main(): if foo > 5: return True return False
def main(): if foo > 5: return True else: return False
def main(): return foo > 5 ```
Remove unnecessary comprehensions for any
and all
```python
any([x.id for x in bar]) all([x.id for x in bar])
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".
```python
logger.info("Login error for %s" % user) logger.info("Login error for %s, %s" % (user_id, name))
logger.info("Login error for {}".format(user)) logger.info("Login error for {}, {}".format(user_id, name))
logger.info(f"Login error for {user}") logger.info(f"Login error for {user_id}, {name}")
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.
```python
foo(**{"bar": True})
foo(bar=True) foo(buzz) foo({"bar foo": True}) ```
```shell
poetry install
s/lint s/test ```
Currently only checks for byte string literals.
```python
"foo".encode()
b"foo" "😀".encode() ```
Instead of assert
ing and catching the exception, use an if statement.
```python
try: assert "@" in bar except AssertionError: ...
if "@" in bar: ... ```
lambda: []
is equivalent to the builtin list
```python
@dataclass class Foo: foo: List[str] = field(default_factory=lambda: [])
@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.
```python
range(0, 10)
range(10) range(x, 10) range(0, 10, x) ```
Bulk create multiple objects instead of executing O(N) queries.
```python
[Item.objects.create(item) for item in items]
[Item.objects.create(item) for item in [bar for bar in buzz]]
(Item.objects.create(item) for item in items)
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.
```python
foo.startswith("foo") or foo.startswith("bar")
foo.endswith("foo") or foo.endswith("bar")
foo.startswith("foo") or str.startswith(foo, "bar")
foo.startswith(("foo", "bar"))
foo.endswith(("foo", "bar"))
foo.startswith("foo") or foo.endswith("bar") ```
You can use astpretty
to dump the AST of a piece of code.
shell
./.venv/bin/astpretty <(pbpaste)
```shell
Flake8PieCheck.version
and pyproject.toml version
rm -rf dist && poetry publish --build ```
Bumps certifi from 2020.12.5 to 2022.12.7.
9e9e840
2022.12.07b81bdb2
2022.09.24939a28f
2022.09.14aca828a
2022.06.15.2de0eae1
Only use importlib.resources's new files() / Traversable API on Python ≥3.11 ...b8eb5e9
2022.06.15.147fb7ab
Fix deprecation warning on Python 3.11 (#199)b0b48e0
fixes #198 -- update link in license9d514b4
2022.06.154151e88
Add py.typed to MANIFEST.in to package in sdist (#196)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
.
py
{c for c in " /.0123456789"}
can be
py
set(" /.0123456789")
Found via codespell .
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.
I think this rule could be pretty general but a basic example:
```python
for id in list({x.bar for x in foo}): ...
for id in {x.bar for x in foo}: ... for id in list({x.bar for x in foo})[:5]: ... ```
```python
not foo == bar
foo != bar ```
Better late than never
New rules from v0.5.0...v0.12.0
precise-exception-handlers
no-len-condition
no-bool-condition
prefer-isinstance-type-compare
no-unnecessary-pass
no-pointless-statements
no-inherit-object
prefer-dataclass
dupe-class-field-definitions
prefer-stdlib-enums
prefer-unique-enums
no-unnecessary-if-expr
no-unnecessary-class
prefer-col-init
no-unnecessary-spread
prefer-simple-return
prefer-simple-any-all
prefer-logging-interpolation
flake8 lint python