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,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/batched-without-explicit-strict
|
|
3
|
+
title: "`itertools.batched()` says `strict=`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: suggest
|
|
6
|
+
fix_reason: "`strict=` exists only from Python 3.13, 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.13 the rule is off" }
|
|
10
|
+
ported_from: [ruff/B911]
|
|
11
|
+
since: 0.1.0
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
`itertools.batched()` is called with `strict=True` or `strict=False`.
|
|
15
|
+
|
|
16
|
+
**Why:** Without it, a short last batch passes silently. The keyword exists from Python 3.13.
|
|
17
|
+
|
|
18
|
+
```codelaw
|
|
19
|
+
native: py/batched-without-explicit-strict
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Examples
|
|
23
|
+
|
|
24
|
+
### Python
|
|
25
|
+
|
|
26
|
+
#### Don't
|
|
27
|
+
|
|
28
|
+
```py
|
|
29
|
+
import itertools
|
|
30
|
+
|
|
31
|
+
batches = itertools.batched(items, 2)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### Do
|
|
35
|
+
|
|
36
|
+
```py
|
|
37
|
+
import itertools
|
|
38
|
+
|
|
39
|
+
batches = itertools.batched(items, 2, strict=True)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
#### Fixed
|
|
43
|
+
|
|
44
|
+
```py
|
|
45
|
+
import itertools
|
|
46
|
+
|
|
47
|
+
batches = itertools.batched(items, 2, strict=False)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Differences from the source
|
|
51
|
+
|
|
52
|
+
Ruff reports this without a fix; codelaw suggests `strict=False`, which keeps today's behaviour. The fix is `suggest` (`codelaw fix --unsafe`), not `auto`: the `target_version` option may be unset, and on Python before 3.13 the keyword raises `TypeError`.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/break-outside-loop
|
|
3
|
+
title: "`break` only inside a loop"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "what should happen instead (return, raise) is the author's choice"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F701]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`break` appears only inside the body of a `for` or `while` loop in the same function.
|
|
13
|
+
|
|
14
|
+
**Why:** Outside a loop it is a `SyntaxError`.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/break-outside-loop
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Use `return` to leave a function, or move the `break` into the loop.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
def stop():
|
|
32
|
+
break
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Do
|
|
36
|
+
|
|
37
|
+
```py
|
|
38
|
+
def stop(items):
|
|
39
|
+
for item in items:
|
|
40
|
+
break
|
|
41
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/cached-instance-method
|
|
3
|
+
title: "No `lru_cache` on methods"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "where the cache should live is a design choice"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B019]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`functools.lru_cache` and `functools.cache` decorate functions and static methods, not instance methods.
|
|
13
|
+
|
|
14
|
+
**Why:** The cache keeps a reference to every `self` it sees, so instances are never freed.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/cached-instance-method
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Move the cached work into a module-level function or a `staticmethod`.
|
|
23
|
+
2. Or keep a per-instance cache in an attribute.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
import functools
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Store:
|
|
36
|
+
@functools.lru_cache
|
|
37
|
+
def get(self, key):
|
|
38
|
+
return key.upper()
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### Do
|
|
42
|
+
|
|
43
|
+
```py
|
|
44
|
+
import functools
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@functools.lru_cache
|
|
48
|
+
def normalise(key):
|
|
49
|
+
return key.upper()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Store:
|
|
53
|
+
def get(self, key):
|
|
54
|
+
return normalise(key)
|
|
55
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/continue-outside-loop
|
|
3
|
+
title: "`continue` only inside a loop"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "what should happen instead is the author's choice"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F702]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`continue` appears only inside the body of a `for` or `while` loop in the same function.
|
|
13
|
+
|
|
14
|
+
**Why:** Outside a loop it is a `SyntaxError`.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/continue-outside-loop
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Move the `continue` into the loop, or restructure the code.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
def skip():
|
|
32
|
+
continue
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Do
|
|
36
|
+
|
|
37
|
+
```py
|
|
38
|
+
def skip(items):
|
|
39
|
+
for item in items:
|
|
40
|
+
continue
|
|
41
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/default-except-not-last
|
|
3
|
+
title: "A bare `except:` is the last handler"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "reordering handlers changes which one runs"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F707]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
In a `try` statement, a bare `except:` comes after every other handler.
|
|
13
|
+
|
|
14
|
+
**Why:** Handlers after a bare `except:` can never run, and Python rejects it as a `SyntaxError`.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/default-except-not-last
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Move the bare `except:` to the end.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
try:
|
|
32
|
+
run()
|
|
33
|
+
except:
|
|
34
|
+
log()
|
|
35
|
+
except ValueError:
|
|
36
|
+
retry()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### Do
|
|
40
|
+
|
|
41
|
+
```py
|
|
42
|
+
try:
|
|
43
|
+
run()
|
|
44
|
+
except ValueError:
|
|
45
|
+
retry()
|
|
46
|
+
except:
|
|
47
|
+
log()
|
|
48
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/duplicate-dict-key-variable
|
|
3
|
+
title: "A dict literal doesn't repeat a variable key"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "which value should win can't be known from the code"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F602]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A dict literal uses the same variable as a key at most once.
|
|
13
|
+
|
|
14
|
+
**Why:** Both entries have the same key at run time, so only the last value is kept.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/duplicate-dict-key-variable
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Decide which entry is meant.
|
|
23
|
+
2. Remove the other one, or use a different key.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
scores = {player: 1, player: 2}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Do
|
|
36
|
+
|
|
37
|
+
```py
|
|
38
|
+
scores = {player: 1, rival: 2}
|
|
39
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/duplicate-dict-key
|
|
3
|
+
title: "A dict literal doesn't repeat a literal key"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "which value should win can't be known from the code"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F601]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A dict literal names each literal key (`"a"`, `1`, `True`) once.
|
|
13
|
+
|
|
14
|
+
**Why:** Only the last value is kept, so the earlier one is silently lost.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/duplicate-dict-key
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Decide which value the key should have.
|
|
23
|
+
2. Remove the other entry, or give it its own key.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
colors = {"red": 1, "red": 2}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Do
|
|
36
|
+
|
|
37
|
+
```py
|
|
38
|
+
colors = {"red": 1, "blue": 2}
|
|
39
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/duplicate-handler-exception
|
|
3
|
+
title: "No exception listed twice in one handler"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: auto
|
|
6
|
+
stability: preview
|
|
7
|
+
ported_from: [ruff/B014]
|
|
8
|
+
since: 0.1.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
An `except` tuple names each exception once.
|
|
12
|
+
|
|
13
|
+
**Why:** A repeated exception is redundant and often a typo for a different one.
|
|
14
|
+
|
|
15
|
+
```codelaw
|
|
16
|
+
native: py/duplicate-handler-exception
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
### Python
|
|
22
|
+
|
|
23
|
+
#### Don't
|
|
24
|
+
|
|
25
|
+
```py
|
|
26
|
+
try:
|
|
27
|
+
run()
|
|
28
|
+
except (ValueError, ValueError, TypeError):
|
|
29
|
+
retry()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
#### Do
|
|
33
|
+
|
|
34
|
+
```py
|
|
35
|
+
try:
|
|
36
|
+
run()
|
|
37
|
+
except (ValueError, TypeError):
|
|
38
|
+
retry()
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Differences from the source
|
|
42
|
+
|
|
43
|
+
Exceptions are compared by name as written; aliases such as `IOError` and `OSError` are not merged.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/duplicate-set-value
|
|
3
|
+
title: "No duplicate items in a set literal"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: auto
|
|
6
|
+
stability: preview
|
|
7
|
+
ported_from: [ruff/B033]
|
|
8
|
+
since: 0.1.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
A set literal lists each literal value once.
|
|
12
|
+
|
|
13
|
+
**Why:** A repeated item is dropped silently and is often a typo for another value.
|
|
14
|
+
|
|
15
|
+
```codelaw
|
|
16
|
+
native: py/duplicate-set-value
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
### Python
|
|
22
|
+
|
|
23
|
+
#### Don't
|
|
24
|
+
|
|
25
|
+
```py
|
|
26
|
+
colors = {"red", "green", "red"}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
#### Do
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
colors = {"red", "green"}
|
|
33
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/duplicate-try-block-exception
|
|
3
|
+
title: "An exception is caught by one handler only"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "which handler's code is meant to run needs a person"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B025]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Within one `try` statement, each exception class appears in only one `except` clause.
|
|
13
|
+
|
|
14
|
+
**Why:** Only the first matching handler runs, so the later one is dead code.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/duplicate-try-block-exception
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Merge the handlers' code into the first one, or remove the dead one.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
try:
|
|
32
|
+
run()
|
|
33
|
+
except ValueError:
|
|
34
|
+
retry()
|
|
35
|
+
except ValueError:
|
|
36
|
+
log()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### Do
|
|
40
|
+
|
|
41
|
+
```py
|
|
42
|
+
try:
|
|
43
|
+
run()
|
|
44
|
+
except ValueError:
|
|
45
|
+
retry()
|
|
46
|
+
log()
|
|
47
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/empty-method-without-abstract-decorator
|
|
3
|
+
title: "Empty methods in an abstract base class are abstract"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "whether the method is a required hook or an optional no-op is a design decision"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B027]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
In a class that inherits from `ABC`, a method whose body is empty is marked `@abstractmethod`.
|
|
13
|
+
|
|
14
|
+
**Why:** An empty, unmarked method lets subclasses forget to implement it.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/empty-method-without-abstract-decorator
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Add `@abstractmethod` if subclasses must implement it.
|
|
23
|
+
2. Otherwise give it a docstring saying it is an optional hook.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
from abc import ABC, abstractmethod
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Shape(ABC):
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def area(self): ...
|
|
38
|
+
|
|
39
|
+
def draw(self):
|
|
40
|
+
pass
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### Do
|
|
44
|
+
|
|
45
|
+
```py
|
|
46
|
+
from abc import ABC, abstractmethod
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Shape(ABC):
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def area(self): ...
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def draw(self): ...
|
|
55
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/except-with-empty-tuple
|
|
3
|
+
title: "No `except ():`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the exceptions to catch are the author's choice"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B029]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
An `except` clause doesn't name an empty tuple.
|
|
13
|
+
|
|
14
|
+
**Why:** `except ():` catches nothing, so the handler never runs.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/except-with-empty-tuple
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Name the exceptions to catch, or remove the handler.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
try:
|
|
32
|
+
run()
|
|
33
|
+
except ():
|
|
34
|
+
log()
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
#### Do
|
|
38
|
+
|
|
39
|
+
```py
|
|
40
|
+
try:
|
|
41
|
+
run()
|
|
42
|
+
except ValueError:
|
|
43
|
+
log()
|
|
44
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/except-with-non-exception-classes
|
|
3
|
+
title: "`except` names exception classes"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the exception class to catch is the author's choice"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B030]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
An `except` clause names exception classes (or tuples of them), not literals or other values.
|
|
13
|
+
|
|
14
|
+
**Why:** Catching anything else raises `TypeError` the moment an exception reaches the handler.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/except-with-non-exception-classes
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Replace the value with the exception class you meant to catch.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
try:
|
|
32
|
+
run()
|
|
33
|
+
except "error":
|
|
34
|
+
log()
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
#### Do
|
|
38
|
+
|
|
39
|
+
```py
|
|
40
|
+
try:
|
|
41
|
+
run()
|
|
42
|
+
except RuntimeError:
|
|
43
|
+
log()
|
|
44
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/f-string-docstring
|
|
3
|
+
title: "Docstrings aren't f-strings"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the docstring's wording has to change to drop the placeholder"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B021]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
The first statement of a module, class or function isn't an f-string meant as its docstring.
|
|
13
|
+
|
|
14
|
+
**Why:** Python treats an f-string there as an expression, so `__doc__` stays empty.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/f-string-docstring
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Remove the `f` prefix and write the text without placeholders.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
def greet():
|
|
32
|
+
f"""Say hello to {NAME}."""
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Do
|
|
36
|
+
|
|
37
|
+
```py
|
|
38
|
+
def greet():
|
|
39
|
+
"""Say hello to the configured name."""
|
|
40
|
+
```
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# py/f-string-missing-placeholders — cases
|
|
2
|
+
|
|
3
|
+
### case: escaped braces become plain braces
|
|
4
|
+
|
|
5
|
+
```py
|
|
6
|
+
x = f"{{literal}}"
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```expect
|
|
10
|
+
1:5 py/f-string-missing-placeholders
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```fixed
|
|
14
|
+
x = "{literal}"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### case: one part of a concatenation has a placeholder
|
|
18
|
+
|
|
19
|
+
```py
|
|
20
|
+
x = f"a" f"{b}"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```expect
|
|
24
|
+
(none)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### case: all parts lack placeholders
|
|
28
|
+
|
|
29
|
+
```py
|
|
30
|
+
x = (f"a" "b")
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```expect
|
|
34
|
+
1:6 py/f-string-missing-placeholders
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```fixed
|
|
38
|
+
x = ("a" "b")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### case: nested f-string in a format spec
|
|
42
|
+
|
|
43
|
+
```py
|
|
44
|
+
x = f"{v:{w}}"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```expect
|
|
48
|
+
(none)
|
|
49
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/f-string-missing-placeholders
|
|
3
|
+
title: "An f-string has a placeholder"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: auto
|
|
6
|
+
stability: preview
|
|
7
|
+
ported_from: [ruff/F541]
|
|
8
|
+
since: 0.1.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
A string with the `f` prefix contains at least one `{…}` placeholder.
|
|
12
|
+
|
|
13
|
+
**Why:** Without one, the prefix does nothing and often means a placeholder was forgotten.
|
|
14
|
+
|
|
15
|
+
```codelaw
|
|
16
|
+
native: py/f-string-missing-placeholders
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
### Python
|
|
22
|
+
|
|
23
|
+
#### Don't
|
|
24
|
+
|
|
25
|
+
```py
|
|
26
|
+
print(f"hello")
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
#### Do
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
print("hello")
|
|
33
|
+
```
|