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,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/format-invalid
|
|
3
|
+
title: "A `.format` string is well formed"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "whether the brace is a field or literal text has to be decided"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F521]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A string literal passed through `.format()` has balanced braces; literal braces are written `{{` and `}}`.
|
|
13
|
+
|
|
14
|
+
**Why:** A stray brace raises `ValueError` when the string is formatted.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/format-invalid
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Close the field, or double a literal brace (`{{`, `}}`).
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
message = "{".format(1)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### Do
|
|
35
|
+
|
|
36
|
+
```py
|
|
37
|
+
message = "{}".format(1)
|
|
38
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/format-missing-argument
|
|
3
|
+
title: "Every `.format` field has an argument"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the missing value has to be supplied"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F524]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A `.format()` call passes an argument for every `{}`, `{0}` and `{name}` field.
|
|
13
|
+
|
|
14
|
+
**Why:** A missing argument raises `IndexError` or `KeyError` when the string is formatted.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/format-missing-argument
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Pass the missing argument, or remove the field.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
message = "{} is {}".format("Ada")
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### Do
|
|
35
|
+
|
|
36
|
+
```py
|
|
37
|
+
message = "{} is {}".format("Ada", 36)
|
|
38
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/format-mixed-numbering
|
|
3
|
+
title: "A `.format` string doesn't mix `{}` and `{0}`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "which numbering the author wants has to be chosen"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F525]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A `.format()` string numbers its fields automatically (`{}`) or explicitly (`{0}`), not both.
|
|
13
|
+
|
|
14
|
+
**Why:** Python raises `ValueError: cannot switch from manual field specification to automatic field numbering`.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/format-mixed-numbering
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Number every field, or none of them.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
message = "{0} is {}".format("Ada", 36)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### Do
|
|
35
|
+
|
|
36
|
+
```py
|
|
37
|
+
message = "{0} is {1}".format("Ada", 36)
|
|
38
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/format-unused-named-argument
|
|
3
|
+
title: "Every keyword passed to `.format` is used"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: auto
|
|
6
|
+
stability: preview
|
|
7
|
+
ported_from: [ruff/F522]
|
|
8
|
+
since: 0.1.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
A `.format()` call has no keyword arguments the string never names.
|
|
12
|
+
|
|
13
|
+
**Why:** An unused keyword is usually a misspelled field name.
|
|
14
|
+
|
|
15
|
+
```codelaw
|
|
16
|
+
native: py/format-unused-named-argument
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
### Python
|
|
22
|
+
|
|
23
|
+
#### Don't
|
|
24
|
+
|
|
25
|
+
```py
|
|
26
|
+
message = "{name}".format(name="Ada", age=36)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
#### Do
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
message = "{name}".format(name="Ada")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Differences from the source
|
|
36
|
+
|
|
37
|
+
The fix is offered only when the removed values are plain names or literals.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/format-unused-positional-argument
|
|
3
|
+
title: "Every positional argument to `.format` is used"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: auto
|
|
6
|
+
stability: preview
|
|
7
|
+
ported_from: [ruff/F523]
|
|
8
|
+
since: 0.1.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
A `.format()` call has no positional arguments the string never refers to.
|
|
12
|
+
|
|
13
|
+
**Why:** An extra argument is usually a missing `{}` field.
|
|
14
|
+
|
|
15
|
+
```codelaw
|
|
16
|
+
native: py/format-unused-positional-argument
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
### Python
|
|
22
|
+
|
|
23
|
+
#### Don't
|
|
24
|
+
|
|
25
|
+
```py
|
|
26
|
+
message = "{}".format("Ada", 36)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
#### Do
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
message = "{}".format("Ada")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Differences from the source
|
|
36
|
+
|
|
37
|
+
The fix removes unused arguments only from the end of the list, and only plain names or literals.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/forward-annotation-syntax-error
|
|
3
|
+
title: "A string annotation is valid Python"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the intended type can't be inferred from broken text"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F722]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A type annotation written as a string parses as a Python expression.
|
|
13
|
+
|
|
14
|
+
**Why:** Type checkers and `typing.get_type_hints()` fail on an annotation they can't parse.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/forward-annotation-syntax-error
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Correct the annotation so it parses.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
def total(values: "list[int") -> int:
|
|
32
|
+
return sum(values)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Do
|
|
36
|
+
|
|
37
|
+
```py
|
|
38
|
+
def total(values: "list[int]") -> int:
|
|
39
|
+
return sum(values)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Differences from the source
|
|
43
|
+
|
|
44
|
+
Strings inside `Literal[...]` and `Annotated[...]` are not treated as annotations.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/function-call-in-default-argument
|
|
3
|
+
title: "No function calls in default arguments"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "whether the value should be computed per call or once at import needs a person"
|
|
7
|
+
stability: preview
|
|
8
|
+
options:
|
|
9
|
+
extend_immutable_calls: { type: "string[]", default: [], description: "more calls whose results are immutable, by qualified name" }
|
|
10
|
+
ported_from: [ruff/B008]
|
|
11
|
+
since: 0.1.0
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
A parameter's default isn't computed by a function call, except calls known to return immutable values (`tuple()`, `frozenset()`, `int()`, `re.compile()`, `datetime.timedelta()`, …).
|
|
15
|
+
|
|
16
|
+
**Why:** The call runs once, when the function is defined, so every call shares the same result — a stale timestamp, or one shared object.
|
|
17
|
+
|
|
18
|
+
```codelaw
|
|
19
|
+
native: py/function-call-in-default-argument
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## How to fix
|
|
23
|
+
|
|
24
|
+
1. Default to `None` and compute the value in the body, or
|
|
25
|
+
2. compute it once into a module-level constant and use that as the default.
|
|
26
|
+
|
|
27
|
+
## Examples
|
|
28
|
+
|
|
29
|
+
### Python
|
|
30
|
+
|
|
31
|
+
#### Don't
|
|
32
|
+
|
|
33
|
+
```py
|
|
34
|
+
import time
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def log(message, when=time.time()):
|
|
38
|
+
print(when, message)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### Do
|
|
42
|
+
|
|
43
|
+
```py
|
|
44
|
+
import time
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def log(message, when=None):
|
|
48
|
+
if when is None:
|
|
49
|
+
when = time.time()
|
|
50
|
+
print(when, message)
|
|
51
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# py/function-uses-loop-variable — cases
|
|
2
|
+
|
|
3
|
+
### case: every read is reported
|
|
4
|
+
|
|
5
|
+
```py
|
|
6
|
+
for i in range(3):
|
|
7
|
+
def f():
|
|
8
|
+
return i + i
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```expect
|
|
12
|
+
3:16 py/function-uses-loop-variable
|
|
13
|
+
3:20 py/function-uses-loop-variable
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### case: key functions run immediately
|
|
17
|
+
|
|
18
|
+
```py
|
|
19
|
+
for i in range(3):
|
|
20
|
+
rows.sort(key=lambda r: r[i])
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```expect
|
|
24
|
+
(none)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### case: map runs immediately
|
|
28
|
+
|
|
29
|
+
```py
|
|
30
|
+
for i in range(3):
|
|
31
|
+
list(map(lambda x: x + i, xs))
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```expect
|
|
35
|
+
(none)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### case: comprehension variable
|
|
39
|
+
|
|
40
|
+
```py
|
|
41
|
+
fs = [lambda: i for i in range(3)]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```expect
|
|
45
|
+
1:15 py/function-uses-loop-variable
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### case: parameter with the same name
|
|
49
|
+
|
|
50
|
+
```py
|
|
51
|
+
for i in range(3):
|
|
52
|
+
f = lambda i: i
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```expect
|
|
56
|
+
(none)
|
|
57
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/function-uses-loop-variable
|
|
3
|
+
title: "Closures don't capture a loop variable by name"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "binding the value now (a default argument, `functools.partial`) changes the function's signature"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B023]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A function or lambda defined in a loop doesn't read a variable the loop rebinds, unless it is called or consumed straight away.
|
|
13
|
+
|
|
14
|
+
**Why:** Python closures look names up when they run, so every closure sees the loop's last value.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/function-uses-loop-variable
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Bind the current value with a default argument (`lambda name=name: …`) or `functools.partial`.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
callbacks = []
|
|
32
|
+
for name in ["a", "b"]:
|
|
33
|
+
callbacks.append(lambda: print(name))
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Do
|
|
37
|
+
|
|
38
|
+
```py
|
|
39
|
+
callbacks = []
|
|
40
|
+
for name in ["a", "b"]:
|
|
41
|
+
callbacks.append(lambda name=name: print(name))
|
|
42
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# py/getattr-with-constant — cases
|
|
2
|
+
|
|
3
|
+
### case: with a default it stays
|
|
4
|
+
|
|
5
|
+
```py
|
|
6
|
+
name = getattr(user, "name", None)
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```expect
|
|
10
|
+
(none)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### case: private names are mangled
|
|
14
|
+
|
|
15
|
+
```py
|
|
16
|
+
x = getattr(self, "__secret")
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```expect
|
|
20
|
+
(none)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### case: not an identifier
|
|
24
|
+
|
|
25
|
+
```py
|
|
26
|
+
x = getattr(obj, "a-b")
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```expect
|
|
30
|
+
(none)
|
|
31
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/getattr-with-constant
|
|
3
|
+
title: "No `getattr` with a constant name"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: auto
|
|
6
|
+
stability: preview
|
|
7
|
+
ported_from: [ruff/B009]
|
|
8
|
+
since: 0.1.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
`getattr(obj, "name")` with a constant, valid attribute name is written `obj.name`.
|
|
12
|
+
|
|
13
|
+
**Why:** `getattr` with a constant name is no safer than attribute access, only harder to read.
|
|
14
|
+
|
|
15
|
+
```codelaw
|
|
16
|
+
native: py/getattr-with-constant
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
### Python
|
|
22
|
+
|
|
23
|
+
#### Don't
|
|
24
|
+
|
|
25
|
+
```py
|
|
26
|
+
name = getattr(user, "name")
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
#### Do
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
name = user.name
|
|
33
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/if-tuple
|
|
3
|
+
title: "`if` doesn't test a tuple"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the intended condition can't be inferred from the tuple"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F634]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
An `if` or `elif` condition isn't a non-empty tuple.
|
|
13
|
+
|
|
14
|
+
**Why:** A non-empty tuple is always true, so the branch always runs.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/if-tuple
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Write the condition you meant, usually the tuple's first element.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
if (ready, "waiting"):
|
|
32
|
+
start()
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Do
|
|
36
|
+
|
|
37
|
+
```py
|
|
38
|
+
if ready:
|
|
39
|
+
start()
|
|
40
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/import-shadowed-by-loop-var
|
|
3
|
+
title: "A loop variable doesn't reuse an imported name"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the new loop variable name is a choice the author has to make"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F402]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A `for` loop's target doesn't rebind a name that an import in the same scope bound.
|
|
13
|
+
|
|
14
|
+
**Why:** After the loop the name no longer refers to the module, so later uses break in ways that are hard to trace.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/import-shadowed-by-loop-var
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Rename the loop variable.
|
|
23
|
+
2. Update its uses inside the loop body.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
import path
|
|
33
|
+
|
|
34
|
+
for path in ["a.txt", "b.txt"]:
|
|
35
|
+
print(path)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
#### Do
|
|
39
|
+
|
|
40
|
+
```py
|
|
41
|
+
import path
|
|
42
|
+
|
|
43
|
+
for name in ["a.txt", "b.txt"]:
|
|
44
|
+
print(path.join(name))
|
|
45
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/invalid-print-syntax
|
|
3
|
+
title: "No Python 2 `print >>`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the rewrite depends on how the Python 2 arguments were meant"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F633]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`print` is called as a function; the Python 2 form `print >> stream, value` isn't used.
|
|
13
|
+
|
|
14
|
+
**Why:** In Python 3 it shifts the `print` function right, raising `TypeError`.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/invalid-print-syntax
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Rewrite as `print(value, file=stream)`.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
import sys
|
|
32
|
+
|
|
33
|
+
print >> sys.stderr, "error"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Do
|
|
37
|
+
|
|
38
|
+
```py
|
|
39
|
+
import sys
|
|
40
|
+
|
|
41
|
+
print("error", file=sys.stderr)
|
|
42
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/is-literal
|
|
3
|
+
title: "Literals are compared with `==`, not `is`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: suggest
|
|
6
|
+
fix_reason: "`==` calls the object's `__eq__`, which can behave differently from identity"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F632]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`is` and `is not` aren't used to compare with a string, bytes or number literal.
|
|
13
|
+
|
|
14
|
+
**Why:** Whether two equal literals are the same object is an implementation detail; Python warns about it with `SyntaxWarning`.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/is-literal
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
### Python
|
|
23
|
+
|
|
24
|
+
#### Don't
|
|
25
|
+
|
|
26
|
+
```py
|
|
27
|
+
if name is "admin":
|
|
28
|
+
grant()
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
#### Do
|
|
32
|
+
|
|
33
|
+
```py
|
|
34
|
+
if name == "admin":
|
|
35
|
+
grant()
|
|
36
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/jump-statement-in-finally
|
|
3
|
+
title: "No `return`, `break` or `continue` in `finally`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "how errors should propagate has to be decided"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B012]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A `finally` block doesn't leave through `return`, `break` or `continue`.
|
|
13
|
+
|
|
14
|
+
**Why:** Leaving `finally` that way silently discards any exception in flight.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/jump-statement-in-finally
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Move the `return`, `break` or `continue` out of the `finally` block.
|
|
23
|
+
2. Handle the exception explicitly with `except` if it should be swallowed.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
def read(path):
|
|
33
|
+
try:
|
|
34
|
+
return load(path)
|
|
35
|
+
finally:
|
|
36
|
+
return None
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### Do
|
|
40
|
+
|
|
41
|
+
```py
|
|
42
|
+
def read(path):
|
|
43
|
+
try:
|
|
44
|
+
return load(path)
|
|
45
|
+
finally:
|
|
46
|
+
close(path)
|
|
47
|
+
```
|