codelaw-rules-python 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codelaw_rules_python/LICENSE +23 -0
- codelaw_rules_python/NOTICE +14 -0
- codelaw_rules_python/README.md +189 -0
- codelaw_rules_python/codelaw-pack.toml +6 -0
- codelaw_rules_python/corpus.toml +103 -0
- codelaw_rules_python/data/python/stdlib.json +331 -0
- codelaw_rules_python/presets/py/conventions.toml +5 -0
- codelaw_rules_python/presets/py/project.toml +4 -0
- codelaw_rules_python/presets/py/recommended.toml +99 -0
- codelaw_rules_python/rules/graph/no-unused-exports.md +69 -0
- codelaw_rules_python/rules/py/abstract-base-class-without-abstract-method.md +50 -0
- codelaw_rules_python/rules/py/all-sorted.cases.md +74 -0
- codelaw_rules_python/rules/py/all-sorted.md +33 -0
- codelaw_rules_python/rules/py/ambiguous-class-name.md +41 -0
- codelaw_rules_python/rules/py/ambiguous-function-name.md +41 -0
- codelaw_rules_python/rules/py/ambiguous-variable-name.cases.md +47 -0
- codelaw_rules_python/rules/py/ambiguous-variable-name.md +39 -0
- codelaw_rules_python/rules/py/assert-false.md +34 -0
- codelaw_rules_python/rules/py/assert-raises-exception.md +51 -0
- codelaw_rules_python/rules/py/assert-tuple.md +38 -0
- codelaw_rules_python/rules/py/assignment-to-os-environ.md +43 -0
- codelaw_rules_python/rules/py/batched-without-explicit-strict.md +52 -0
- codelaw_rules_python/rules/py/break-outside-loop.md +41 -0
- codelaw_rules_python/rules/py/cached-instance-method.md +55 -0
- codelaw_rules_python/rules/py/continue-outside-loop.md +41 -0
- codelaw_rules_python/rules/py/default-except-not-last.md +48 -0
- codelaw_rules_python/rules/py/duplicate-dict-key-variable.md +39 -0
- codelaw_rules_python/rules/py/duplicate-dict-key.md +39 -0
- codelaw_rules_python/rules/py/duplicate-handler-exception.md +43 -0
- codelaw_rules_python/rules/py/duplicate-set-value.md +33 -0
- codelaw_rules_python/rules/py/duplicate-try-block-exception.md +47 -0
- codelaw_rules_python/rules/py/empty-method-without-abstract-decorator.md +55 -0
- codelaw_rules_python/rules/py/except-with-empty-tuple.md +44 -0
- codelaw_rules_python/rules/py/except-with-non-exception-classes.md +44 -0
- codelaw_rules_python/rules/py/f-string-docstring.md +40 -0
- codelaw_rules_python/rules/py/f-string-missing-placeholders.cases.md +49 -0
- codelaw_rules_python/rules/py/f-string-missing-placeholders.md +33 -0
- codelaw_rules_python/rules/py/format-invalid.md +38 -0
- codelaw_rules_python/rules/py/format-missing-argument.md +38 -0
- codelaw_rules_python/rules/py/format-mixed-numbering.md +38 -0
- codelaw_rules_python/rules/py/format-unused-named-argument.md +37 -0
- codelaw_rules_python/rules/py/format-unused-positional-argument.md +37 -0
- codelaw_rules_python/rules/py/forward-annotation-syntax-error.md +44 -0
- codelaw_rules_python/rules/py/function-call-in-default-argument.md +51 -0
- codelaw_rules_python/rules/py/function-uses-loop-variable.cases.md +57 -0
- codelaw_rules_python/rules/py/function-uses-loop-variable.md +42 -0
- codelaw_rules_python/rules/py/getattr-with-constant.cases.md +31 -0
- codelaw_rules_python/rules/py/getattr-with-constant.md +33 -0
- codelaw_rules_python/rules/py/if-tuple.md +40 -0
- codelaw_rules_python/rules/py/import-shadowed-by-loop-var.md +45 -0
- codelaw_rules_python/rules/py/invalid-print-syntax.md +42 -0
- codelaw_rules_python/rules/py/is-literal.md +36 -0
- codelaw_rules_python/rules/py/jump-statement-in-finally.cases.md +15 -0
- codelaw_rules_python/rules/py/jump-statement-in-finally.md +47 -0
- codelaw_rules_python/rules/py/lambda-assignment.cases.md +48 -0
- codelaw_rules_python/rules/py/lambda-assignment.md +35 -0
- codelaw_rules_python/rules/py/late-future-import.md +40 -0
- codelaw_rules_python/rules/py/loop-variable-overrides-iterator.md +41 -0
- codelaw_rules_python/rules/py/map-without-explicit-strict.md +46 -0
- codelaw_rules_python/rules/py/module-import-not-at-top.cases.md +64 -0
- codelaw_rules_python/rules/py/module-import-not-at-top.md +46 -0
- codelaw_rules_python/rules/py/multiple-imports-on-one-line.md +38 -0
- codelaw_rules_python/rules/py/multiple-starred-expressions.md +38 -0
- codelaw_rules_python/rules/py/mutable-contextvar-default.md +42 -0
- codelaw_rules_python/rules/py/nested-star-import.md +42 -0
- codelaw_rules_python/rules/py/no-bare-except.cases.md +37 -0
- codelaw_rules_python/rules/py/no-bare-except.md +44 -0
- codelaw_rules_python/rules/py/no-explicit-stacklevel.md +38 -0
- codelaw_rules_python/rules/py/no-mutable-default.cases.md +58 -0
- codelaw_rules_python/rules/py/no-mutable-default.md +44 -0
- codelaw_rules_python/rules/py/no-print.cases.md +40 -0
- codelaw_rules_python/rules/py/no-print.md +42 -0
- codelaw_rules_python/rules/py/no-star-import.cases.md +11 -0
- codelaw_rules_python/rules/py/no-star-import.md +47 -0
- codelaw_rules_python/rules/py/no-tunable-literals.cases.md +54 -0
- codelaw_rules_python/rules/py/no-tunable-literals.md +58 -0
- codelaw_rules_python/rules/py/none-comparison.md +36 -0
- codelaw_rules_python/rules/py/not-in-test.md +35 -0
- codelaw_rules_python/rules/py/not-is-test.md +35 -0
- codelaw_rules_python/rules/py/percent-format-count-mismatch.md +38 -0
- codelaw_rules_python/rules/py/percent-format-expected-mapping.md +38 -0
- codelaw_rules_python/rules/py/percent-format-expected-sequence.md +38 -0
- codelaw_rules_python/rules/py/percent-format-invalid.md +38 -0
- codelaw_rules_python/rules/py/percent-format-missing-argument.md +38 -0
- codelaw_rules_python/rules/py/percent-format-mixed-placeholders.md +38 -0
- codelaw_rules_python/rules/py/percent-format-star-requires-sequence.md +38 -0
- codelaw_rules_python/rules/py/percent-format-unsupported-character.md +38 -0
- codelaw_rules_python/rules/py/percent-format-unused-named-argument.md +37 -0
- codelaw_rules_python/rules/py/raise-literal.md +38 -0
- codelaw_rules_python/rules/py/raise-not-implemented.md +38 -0
- codelaw_rules_python/rules/py/raise-without-from-inside-except.cases.md +46 -0
- codelaw_rules_python/rules/py/raise-without-from-inside-except.md +45 -0
- codelaw_rules_python/rules/py/re-sub-positional-args.md +42 -0
- codelaw_rules_python/rules/py/redefined-while-unused.cases.md +186 -0
- codelaw_rules_python/rules/py/redefined-while-unused.md +53 -0
- codelaw_rules_python/rules/py/redundant-tuple-in-exception-handler.md +39 -0
- codelaw_rules_python/rules/py/return-outside-function.md +41 -0
- codelaw_rules_python/rules/py/reuse-of-groupby-generator.cases.md +40 -0
- codelaw_rules_python/rules/py/reuse-of-groupby-generator.md +46 -0
- codelaw_rules_python/rules/py/setattr-with-constant.md +33 -0
- codelaw_rules_python/rules/py/star-arg-unpacking-after-keyword-arg.md +38 -0
- codelaw_rules_python/rules/py/star-import-usage.cases.md +35 -0
- codelaw_rules_python/rules/py/star-import-usage.md +43 -0
- codelaw_rules_python/rules/py/static-key-dict-comprehension.md +38 -0
- codelaw_rules_python/rules/py/strip-with-multi-characters.md +39 -0
- codelaw_rules_python/rules/py/too-many-expressions-in-star-assignment.md +38 -0
- codelaw_rules_python/rules/py/true-false-comparison.cases.md +33 -0
- codelaw_rules_python/rules/py/true-false-comparison.md +36 -0
- codelaw_rules_python/rules/py/type-comparison.md +41 -0
- codelaw_rules_python/rules/py/unary-prefix-increment.md +40 -0
- codelaw_rules_python/rules/py/undefined-export.md +55 -0
- codelaw_rules_python/rules/py/undefined-future-feature.md +39 -0
- codelaw_rules_python/rules/py/undefined-local.cases.md +80 -0
- codelaw_rules_python/rules/py/undefined-local.md +48 -0
- codelaw_rules_python/rules/py/undefined-name.cases.md +132 -0
- codelaw_rules_python/rules/py/undefined-name.md +42 -0
- codelaw_rules_python/rules/py/unintentional-type-annotation.cases.md +13 -0
- codelaw_rules_python/rules/py/unintentional-type-annotation.md +38 -0
- codelaw_rules_python/rules/py/unreliable-callable-check.md +36 -0
- codelaw_rules_python/rules/py/unsorted-imports.cases.md +159 -0
- codelaw_rules_python/rules/py/unsorted-imports.md +60 -0
- codelaw_rules_python/rules/py/unused-annotation.md +36 -0
- codelaw_rules_python/rules/py/unused-import.cases.md +289 -0
- codelaw_rules_python/rules/py/unused-import.md +45 -0
- codelaw_rules_python/rules/py/unused-loop-control-variable.cases.md +30 -0
- codelaw_rules_python/rules/py/unused-loop-control-variable.md +36 -0
- codelaw_rules_python/rules/py/unused-variable.cases.md +169 -0
- codelaw_rules_python/rules/py/unused-variable.md +38 -0
- codelaw_rules_python/rules/py/useless-comparison.md +41 -0
- codelaw_rules_python/rules/py/useless-contextlib-suppress.md +44 -0
- codelaw_rules_python/rules/py/useless-expression.cases.md +57 -0
- codelaw_rules_python/rules/py/useless-expression.md +45 -0
- codelaw_rules_python/rules/py/yield-outside-function.md +44 -0
- codelaw_rules_python/rules/py/zip-without-explicit-strict.cases.md +26 -0
- codelaw_rules_python/rules/py/zip-without-explicit-strict.md +46 -0
- codelaw_rules_python-0.2.0.dist-info/METADATA +199 -0
- codelaw_rules_python-0.2.0.dist-info/RECORD +139 -0
- codelaw_rules_python-0.2.0.dist-info/WHEEL +4 -0
- codelaw_rules_python-0.2.0.dist-info/licenses/LICENSE +23 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# py/lambda-assignment — cases
|
|
2
|
+
|
|
3
|
+
### case: in a class body
|
|
4
|
+
|
|
5
|
+
```py
|
|
6
|
+
class C:
|
|
7
|
+
m = lambda self: 1
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```expect
|
|
11
|
+
2:5 py/lambda-assignment
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```fixed
|
|
15
|
+
class C:
|
|
16
|
+
def m(self):
|
|
17
|
+
return 1
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### case: tuple targets are fine
|
|
21
|
+
|
|
22
|
+
```py
|
|
23
|
+
a, b = lambda: 0, 1
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```expect
|
|
27
|
+
(none)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### case: lambda as an argument
|
|
31
|
+
|
|
32
|
+
```py
|
|
33
|
+
items.sort(key=lambda x: x[1])
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```expect
|
|
37
|
+
(none)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### case: attribute targets are fine
|
|
41
|
+
|
|
42
|
+
```py
|
|
43
|
+
obj.handler = lambda: None
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```expect
|
|
47
|
+
(none)
|
|
48
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/lambda-assignment
|
|
3
|
+
title: "Define functions with `def`, not a lambda"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: suggest
|
|
6
|
+
fix_reason: "an annotation on the assigned name is dropped by the rewrite"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/E731]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A name is bound to a function with `def`, not by assigning a `lambda`.
|
|
13
|
+
|
|
14
|
+
**Why:** A `def` has a real name in tracebacks, can have a docstring, and reads as a definition.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/lambda-assignment
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
### Python
|
|
23
|
+
|
|
24
|
+
#### Don't
|
|
25
|
+
|
|
26
|
+
```py
|
|
27
|
+
double = lambda x: x * 2
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### Do
|
|
31
|
+
|
|
32
|
+
```py
|
|
33
|
+
def double(x):
|
|
34
|
+
return x * 2
|
|
35
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/late-future-import
|
|
3
|
+
title: "`from __future__` imports come first"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "moving statements above it can change the order the module runs in"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F404]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`from __future__ import …` statements come before any other statement except the module docstring.
|
|
13
|
+
|
|
14
|
+
**Why:** Python rejects a late `__future__` import with a `SyntaxError`.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/late-future-import
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Move the `from __future__` import to the top, after the docstring.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
import os
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Do
|
|
36
|
+
|
|
37
|
+
```py
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
import os
|
|
40
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/loop-variable-overrides-iterator
|
|
3
|
+
title: "A loop variable doesn't replace what it iterates"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the new name is the author's choice"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B020]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A `for` loop's target doesn't reuse a name read in the iterable expression.
|
|
13
|
+
|
|
14
|
+
**Why:** After the loop the name holds the last item, not the collection, which breaks later uses.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/loop-variable-overrides-iterator
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Give the loop variable its own name.
|
|
23
|
+
2. Update its uses in the loop body.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
for items in items:
|
|
33
|
+
print(items)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Do
|
|
37
|
+
|
|
38
|
+
```py
|
|
39
|
+
for item in items:
|
|
40
|
+
print(item)
|
|
41
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/map-without-explicit-strict
|
|
3
|
+
title: "`map()` over several iterables says `strict=`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: suggest
|
|
6
|
+
fix_reason: "`strict=` exists only from Python 3.14, and codelaw cannot tell which Python the code runs on: on an older one the added keyword raises `TypeError`"
|
|
7
|
+
stability: preview
|
|
8
|
+
options:
|
|
9
|
+
target_version: { type: string, description: "the oldest Python the code supports; below 3.14 the rule is off" }
|
|
10
|
+
ported_from: [ruff/B912]
|
|
11
|
+
since: 0.1.0
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
`map()` with two or more iterables is called with `strict=True` or `strict=False`.
|
|
15
|
+
|
|
16
|
+
**Why:** Without it, `map` silently stops at the shortest input. The keyword exists from Python 3.14.
|
|
17
|
+
|
|
18
|
+
```codelaw
|
|
19
|
+
native: py/map-without-explicit-strict
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Examples
|
|
23
|
+
|
|
24
|
+
### Python
|
|
25
|
+
|
|
26
|
+
#### Don't
|
|
27
|
+
|
|
28
|
+
```py
|
|
29
|
+
sums = map(add, xs, ys)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
#### Do
|
|
33
|
+
|
|
34
|
+
```py
|
|
35
|
+
sums = map(add, xs, ys, strict=True)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
#### Fixed
|
|
39
|
+
|
|
40
|
+
```py
|
|
41
|
+
sums = map(add, xs, ys, strict=False)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Differences from the source
|
|
45
|
+
|
|
46
|
+
The fix is `suggest` (`codelaw fix --unsafe`), not `auto`: the `target_version` option may be unset, and on Python before 3.14 the keyword raises `TypeError`.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# py/module-import-not-at-top — cases
|
|
2
|
+
|
|
3
|
+
### case: sys.path change before imports
|
|
4
|
+
|
|
5
|
+
```py
|
|
6
|
+
import sys
|
|
7
|
+
sys.path.insert(0, 'lib')
|
|
8
|
+
import local_module
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```expect
|
|
12
|
+
(none)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### case: os.environ change before imports
|
|
16
|
+
|
|
17
|
+
```py
|
|
18
|
+
import os
|
|
19
|
+
os.environ['X'] = '1'
|
|
20
|
+
import other
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```expect
|
|
24
|
+
(none)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### case: conditional imports
|
|
28
|
+
|
|
29
|
+
```py
|
|
30
|
+
try:
|
|
31
|
+
import ujson as json
|
|
32
|
+
except ImportError:
|
|
33
|
+
import json
|
|
34
|
+
import os
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```expect
|
|
38
|
+
(none)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### case: dunders and docstring
|
|
42
|
+
|
|
43
|
+
```py
|
|
44
|
+
"""Doc."""
|
|
45
|
+
__all__ = []
|
|
46
|
+
__version__ = '1'
|
|
47
|
+
import os
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```expect
|
|
51
|
+
(none)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### case: imports inside functions are fine
|
|
55
|
+
|
|
56
|
+
```py
|
|
57
|
+
x = 1
|
|
58
|
+
def f():
|
|
59
|
+
import os
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```expect
|
|
63
|
+
(none)
|
|
64
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/module-import-not-at-top
|
|
3
|
+
title: "Module-level imports come first"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "moving an import above other code changes the order the module runs in"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/E402]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Module-level imports come before other code. A docstring, `__future__` imports, dunder assignments (`__all__`, `__version__`), `if`/`try`/`with` blocks and changes to `sys.path` or `os.environ` may come before them.
|
|
13
|
+
|
|
14
|
+
**Why:** Imports scattered through a module hide its dependencies, and code that runs before them can change what they load.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/module-import-not-at-top
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Move the import up to the module's import section.
|
|
23
|
+
2. If it must run after the code above it, keep it and suppress the finding with a comment saying why.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
import os
|
|
33
|
+
|
|
34
|
+
print(os.name)
|
|
35
|
+
|
|
36
|
+
import sys
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### Do
|
|
40
|
+
|
|
41
|
+
```py
|
|
42
|
+
import os
|
|
43
|
+
import sys
|
|
44
|
+
|
|
45
|
+
print(os.name)
|
|
46
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/multiple-imports-on-one-line
|
|
3
|
+
title: "One module per `import` statement"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: auto
|
|
6
|
+
stability: preview
|
|
7
|
+
ported_from: [ruff/E401]
|
|
8
|
+
since: 0.1.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Each `import` statement imports one module; `import os, sys` is split into one line per module.
|
|
12
|
+
|
|
13
|
+
**Why:** One import per line keeps diffs and merges clean and lets import sorting work line by line.
|
|
14
|
+
|
|
15
|
+
```codelaw
|
|
16
|
+
native: py/multiple-imports-on-one-line
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
### Python
|
|
22
|
+
|
|
23
|
+
#### Don't
|
|
24
|
+
|
|
25
|
+
```py
|
|
26
|
+
import os, sys
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
#### Do
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
import os
|
|
33
|
+
import sys
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Differences from the source
|
|
37
|
+
|
|
38
|
+
`from module import a, b` is not affected; it imports from one module.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/multiple-starred-expressions
|
|
3
|
+
title: "One starred target per assignment"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "which target should collect the rest has to be chosen"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F622]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
An unpacking assignment has at most one starred target.
|
|
13
|
+
|
|
14
|
+
**Why:** Two starred targets are ambiguous and a `SyntaxError`.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/multiple-starred-expressions
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Keep the star on one target only.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
*head, *tail = values
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### Do
|
|
35
|
+
|
|
36
|
+
```py
|
|
37
|
+
head, *tail = values
|
|
38
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/mutable-contextvar-default
|
|
3
|
+
title: "No mutable `ContextVar` defaults"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "whether to use an immutable value or set a fresh one per context is a design choice"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B039]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A `ContextVar`'s `default=` isn't a list, dict, set or the result of a call.
|
|
13
|
+
|
|
14
|
+
**Why:** The default is one object shared by every context, so mutating it leaks state between them.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/mutable-contextvar-default
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Use an immutable default, or no default and set a fresh value in each context.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
from contextvars import ContextVar
|
|
32
|
+
|
|
33
|
+
items = ContextVar("items", default=[])
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Do
|
|
37
|
+
|
|
38
|
+
```py
|
|
39
|
+
from contextvars import ContextVar
|
|
40
|
+
|
|
41
|
+
items = ContextVar("items", default=())
|
|
42
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/nested-star-import
|
|
3
|
+
title: "`import *` only at module level"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the names to import must be chosen by reading the function"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F406]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`from module import *` appears only at the top level of a module.
|
|
13
|
+
|
|
14
|
+
**Why:** Inside a function or class a star import is a `SyntaxError` in Python 3.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/nested-star-import
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Replace the star with the names the function uses.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
def paths():
|
|
32
|
+
from os.path import *
|
|
33
|
+
return join("a", "b")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Do
|
|
37
|
+
|
|
38
|
+
```py
|
|
39
|
+
def paths():
|
|
40
|
+
from os.path import join
|
|
41
|
+
return join("a", "b")
|
|
42
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# py/no-bare-except — cases
|
|
2
|
+
|
|
3
|
+
### case: a handler that re-raises
|
|
4
|
+
|
|
5
|
+
```py
|
|
6
|
+
try:
|
|
7
|
+
run()
|
|
8
|
+
except:
|
|
9
|
+
cleanup()
|
|
10
|
+
raise
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```expect
|
|
14
|
+
(none)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### case: a conditional re-raise still swallows
|
|
18
|
+
|
|
19
|
+
```py
|
|
20
|
+
try:
|
|
21
|
+
run()
|
|
22
|
+
except:
|
|
23
|
+
if strict:
|
|
24
|
+
raise
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```expect
|
|
28
|
+
3:1 py/no-bare-except
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```fixed
|
|
32
|
+
try:
|
|
33
|
+
run()
|
|
34
|
+
except Exception:
|
|
35
|
+
if strict:
|
|
36
|
+
raise
|
|
37
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/no-bare-except
|
|
3
|
+
title: "No bare `except:`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: suggest
|
|
6
|
+
fix_reason: "`except Exception:` no longer catches `KeyboardInterrupt` and `SystemExit`"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/E722]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Every `except` clause names the exceptions it handles.
|
|
13
|
+
|
|
14
|
+
**Why:** A bare `except:` also catches `KeyboardInterrupt` and `SystemExit`, so Ctrl-C and `sys.exit()` stop working inside it.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/no-bare-except
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
### Python
|
|
23
|
+
|
|
24
|
+
#### Don't
|
|
25
|
+
|
|
26
|
+
```py
|
|
27
|
+
try:
|
|
28
|
+
run()
|
|
29
|
+
except:
|
|
30
|
+
log()
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
#### Do
|
|
34
|
+
|
|
35
|
+
```py
|
|
36
|
+
try:
|
|
37
|
+
run()
|
|
38
|
+
except Exception:
|
|
39
|
+
log()
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Differences from the source
|
|
43
|
+
|
|
44
|
+
As in Ruff, a handler whose body re-raises with a bare `raise` is allowed: it doesn't swallow anything.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/no-explicit-stacklevel
|
|
3
|
+
title: "`warnings.warn` sets `stacklevel`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: suggest
|
|
6
|
+
fix_reason: "`stacklevel=2` is right for a warning about the caller; deeper helpers need a higher value"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B028]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`warnings.warn()` is called with `stacklevel=` (or `skip_file_prefixes=`).
|
|
13
|
+
|
|
14
|
+
**Why:** The default `stacklevel=1` points the warning at the line inside your library, not at the caller who needs to change.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/no-explicit-stacklevel
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
### Python
|
|
23
|
+
|
|
24
|
+
#### Don't
|
|
25
|
+
|
|
26
|
+
```py
|
|
27
|
+
import warnings
|
|
28
|
+
|
|
29
|
+
warnings.warn("old API")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
#### Do
|
|
33
|
+
|
|
34
|
+
```py
|
|
35
|
+
import warnings
|
|
36
|
+
|
|
37
|
+
warnings.warn("old API", stacklevel=2)
|
|
38
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# py/no-mutable-default — cases
|
|
2
|
+
|
|
3
|
+
### case: after a docstring
|
|
4
|
+
|
|
5
|
+
```py
|
|
6
|
+
def f(x={}):
|
|
7
|
+
"""Doc."""
|
|
8
|
+
return x
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```expect
|
|
12
|
+
1:9 py/no-mutable-default
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```fixed
|
|
16
|
+
def f(x=None):
|
|
17
|
+
"""Doc."""
|
|
18
|
+
if x is None:
|
|
19
|
+
x = {}
|
|
20
|
+
return x
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### case: constructor calls
|
|
24
|
+
|
|
25
|
+
```py
|
|
26
|
+
def f(a=set(), b=dict(k=1), c=list()):
|
|
27
|
+
pass
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```expect
|
|
31
|
+
1:9 py/no-mutable-default
|
|
32
|
+
1:18 py/no-mutable-default
|
|
33
|
+
1:31 py/no-mutable-default
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### case: a default that reads the outer name: no edit
|
|
37
|
+
|
|
38
|
+
```py
|
|
39
|
+
def outer(lnum):
|
|
40
|
+
def reader(lnum=[lnum]):
|
|
41
|
+
return lnum
|
|
42
|
+
return reader
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```expect
|
|
46
|
+
2:21 py/no-mutable-default
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### case: immutable defaults
|
|
50
|
+
|
|
51
|
+
```py
|
|
52
|
+
def f(a=(), b=frozenset(), c=None, d=""):
|
|
53
|
+
pass
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```expect
|
|
57
|
+
(none)
|
|
58
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/no-mutable-default
|
|
3
|
+
title: "No mutable default arguments"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: suggest
|
|
6
|
+
fix_reason: "callers that relied on the shared object, or passed `None` on purpose, behave differently"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B006]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A parameter's default isn't a list, dict or set (literal, comprehension or constructor call).
|
|
13
|
+
|
|
14
|
+
**Why:** The default is created once, when the function is defined, and every call shares and mutates the same object.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/no-mutable-default
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
### Python
|
|
23
|
+
|
|
24
|
+
#### Don't
|
|
25
|
+
|
|
26
|
+
```py
|
|
27
|
+
def append(item, items=[]):
|
|
28
|
+
items.append(item)
|
|
29
|
+
return items
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
#### Do
|
|
33
|
+
|
|
34
|
+
```py
|
|
35
|
+
def append(item, items=None):
|
|
36
|
+
if items is None:
|
|
37
|
+
items = []
|
|
38
|
+
items.append(item)
|
|
39
|
+
return items
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Differences from the source
|
|
43
|
+
|
|
44
|
+
The fix is offered only for unannotated parameters whose default doesn't read the parameter's own name; Ruff also rewrites an annotation to `T | None`.
|