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.
Files changed (139) hide show
  1. codelaw_rules_python/LICENSE +23 -0
  2. codelaw_rules_python/NOTICE +14 -0
  3. codelaw_rules_python/README.md +189 -0
  4. codelaw_rules_python/codelaw-pack.toml +6 -0
  5. codelaw_rules_python/corpus.toml +103 -0
  6. codelaw_rules_python/data/python/stdlib.json +331 -0
  7. codelaw_rules_python/presets/py/conventions.toml +5 -0
  8. codelaw_rules_python/presets/py/project.toml +4 -0
  9. codelaw_rules_python/presets/py/recommended.toml +99 -0
  10. codelaw_rules_python/rules/graph/no-unused-exports.md +69 -0
  11. codelaw_rules_python/rules/py/abstract-base-class-without-abstract-method.md +50 -0
  12. codelaw_rules_python/rules/py/all-sorted.cases.md +74 -0
  13. codelaw_rules_python/rules/py/all-sorted.md +33 -0
  14. codelaw_rules_python/rules/py/ambiguous-class-name.md +41 -0
  15. codelaw_rules_python/rules/py/ambiguous-function-name.md +41 -0
  16. codelaw_rules_python/rules/py/ambiguous-variable-name.cases.md +47 -0
  17. codelaw_rules_python/rules/py/ambiguous-variable-name.md +39 -0
  18. codelaw_rules_python/rules/py/assert-false.md +34 -0
  19. codelaw_rules_python/rules/py/assert-raises-exception.md +51 -0
  20. codelaw_rules_python/rules/py/assert-tuple.md +38 -0
  21. codelaw_rules_python/rules/py/assignment-to-os-environ.md +43 -0
  22. codelaw_rules_python/rules/py/batched-without-explicit-strict.md +52 -0
  23. codelaw_rules_python/rules/py/break-outside-loop.md +41 -0
  24. codelaw_rules_python/rules/py/cached-instance-method.md +55 -0
  25. codelaw_rules_python/rules/py/continue-outside-loop.md +41 -0
  26. codelaw_rules_python/rules/py/default-except-not-last.md +48 -0
  27. codelaw_rules_python/rules/py/duplicate-dict-key-variable.md +39 -0
  28. codelaw_rules_python/rules/py/duplicate-dict-key.md +39 -0
  29. codelaw_rules_python/rules/py/duplicate-handler-exception.md +43 -0
  30. codelaw_rules_python/rules/py/duplicate-set-value.md +33 -0
  31. codelaw_rules_python/rules/py/duplicate-try-block-exception.md +47 -0
  32. codelaw_rules_python/rules/py/empty-method-without-abstract-decorator.md +55 -0
  33. codelaw_rules_python/rules/py/except-with-empty-tuple.md +44 -0
  34. codelaw_rules_python/rules/py/except-with-non-exception-classes.md +44 -0
  35. codelaw_rules_python/rules/py/f-string-docstring.md +40 -0
  36. codelaw_rules_python/rules/py/f-string-missing-placeholders.cases.md +49 -0
  37. codelaw_rules_python/rules/py/f-string-missing-placeholders.md +33 -0
  38. codelaw_rules_python/rules/py/format-invalid.md +38 -0
  39. codelaw_rules_python/rules/py/format-missing-argument.md +38 -0
  40. codelaw_rules_python/rules/py/format-mixed-numbering.md +38 -0
  41. codelaw_rules_python/rules/py/format-unused-named-argument.md +37 -0
  42. codelaw_rules_python/rules/py/format-unused-positional-argument.md +37 -0
  43. codelaw_rules_python/rules/py/forward-annotation-syntax-error.md +44 -0
  44. codelaw_rules_python/rules/py/function-call-in-default-argument.md +51 -0
  45. codelaw_rules_python/rules/py/function-uses-loop-variable.cases.md +57 -0
  46. codelaw_rules_python/rules/py/function-uses-loop-variable.md +42 -0
  47. codelaw_rules_python/rules/py/getattr-with-constant.cases.md +31 -0
  48. codelaw_rules_python/rules/py/getattr-with-constant.md +33 -0
  49. codelaw_rules_python/rules/py/if-tuple.md +40 -0
  50. codelaw_rules_python/rules/py/import-shadowed-by-loop-var.md +45 -0
  51. codelaw_rules_python/rules/py/invalid-print-syntax.md +42 -0
  52. codelaw_rules_python/rules/py/is-literal.md +36 -0
  53. codelaw_rules_python/rules/py/jump-statement-in-finally.cases.md +15 -0
  54. codelaw_rules_python/rules/py/jump-statement-in-finally.md +47 -0
  55. codelaw_rules_python/rules/py/lambda-assignment.cases.md +48 -0
  56. codelaw_rules_python/rules/py/lambda-assignment.md +35 -0
  57. codelaw_rules_python/rules/py/late-future-import.md +40 -0
  58. codelaw_rules_python/rules/py/loop-variable-overrides-iterator.md +41 -0
  59. codelaw_rules_python/rules/py/map-without-explicit-strict.md +46 -0
  60. codelaw_rules_python/rules/py/module-import-not-at-top.cases.md +64 -0
  61. codelaw_rules_python/rules/py/module-import-not-at-top.md +46 -0
  62. codelaw_rules_python/rules/py/multiple-imports-on-one-line.md +38 -0
  63. codelaw_rules_python/rules/py/multiple-starred-expressions.md +38 -0
  64. codelaw_rules_python/rules/py/mutable-contextvar-default.md +42 -0
  65. codelaw_rules_python/rules/py/nested-star-import.md +42 -0
  66. codelaw_rules_python/rules/py/no-bare-except.cases.md +37 -0
  67. codelaw_rules_python/rules/py/no-bare-except.md +44 -0
  68. codelaw_rules_python/rules/py/no-explicit-stacklevel.md +38 -0
  69. codelaw_rules_python/rules/py/no-mutable-default.cases.md +58 -0
  70. codelaw_rules_python/rules/py/no-mutable-default.md +44 -0
  71. codelaw_rules_python/rules/py/no-print.cases.md +40 -0
  72. codelaw_rules_python/rules/py/no-print.md +42 -0
  73. codelaw_rules_python/rules/py/no-star-import.cases.md +11 -0
  74. codelaw_rules_python/rules/py/no-star-import.md +47 -0
  75. codelaw_rules_python/rules/py/no-tunable-literals.cases.md +54 -0
  76. codelaw_rules_python/rules/py/no-tunable-literals.md +58 -0
  77. codelaw_rules_python/rules/py/none-comparison.md +36 -0
  78. codelaw_rules_python/rules/py/not-in-test.md +35 -0
  79. codelaw_rules_python/rules/py/not-is-test.md +35 -0
  80. codelaw_rules_python/rules/py/percent-format-count-mismatch.md +38 -0
  81. codelaw_rules_python/rules/py/percent-format-expected-mapping.md +38 -0
  82. codelaw_rules_python/rules/py/percent-format-expected-sequence.md +38 -0
  83. codelaw_rules_python/rules/py/percent-format-invalid.md +38 -0
  84. codelaw_rules_python/rules/py/percent-format-missing-argument.md +38 -0
  85. codelaw_rules_python/rules/py/percent-format-mixed-placeholders.md +38 -0
  86. codelaw_rules_python/rules/py/percent-format-star-requires-sequence.md +38 -0
  87. codelaw_rules_python/rules/py/percent-format-unsupported-character.md +38 -0
  88. codelaw_rules_python/rules/py/percent-format-unused-named-argument.md +37 -0
  89. codelaw_rules_python/rules/py/raise-literal.md +38 -0
  90. codelaw_rules_python/rules/py/raise-not-implemented.md +38 -0
  91. codelaw_rules_python/rules/py/raise-without-from-inside-except.cases.md +46 -0
  92. codelaw_rules_python/rules/py/raise-without-from-inside-except.md +45 -0
  93. codelaw_rules_python/rules/py/re-sub-positional-args.md +42 -0
  94. codelaw_rules_python/rules/py/redefined-while-unused.cases.md +186 -0
  95. codelaw_rules_python/rules/py/redefined-while-unused.md +53 -0
  96. codelaw_rules_python/rules/py/redundant-tuple-in-exception-handler.md +39 -0
  97. codelaw_rules_python/rules/py/return-outside-function.md +41 -0
  98. codelaw_rules_python/rules/py/reuse-of-groupby-generator.cases.md +40 -0
  99. codelaw_rules_python/rules/py/reuse-of-groupby-generator.md +46 -0
  100. codelaw_rules_python/rules/py/setattr-with-constant.md +33 -0
  101. codelaw_rules_python/rules/py/star-arg-unpacking-after-keyword-arg.md +38 -0
  102. codelaw_rules_python/rules/py/star-import-usage.cases.md +35 -0
  103. codelaw_rules_python/rules/py/star-import-usage.md +43 -0
  104. codelaw_rules_python/rules/py/static-key-dict-comprehension.md +38 -0
  105. codelaw_rules_python/rules/py/strip-with-multi-characters.md +39 -0
  106. codelaw_rules_python/rules/py/too-many-expressions-in-star-assignment.md +38 -0
  107. codelaw_rules_python/rules/py/true-false-comparison.cases.md +33 -0
  108. codelaw_rules_python/rules/py/true-false-comparison.md +36 -0
  109. codelaw_rules_python/rules/py/type-comparison.md +41 -0
  110. codelaw_rules_python/rules/py/unary-prefix-increment.md +40 -0
  111. codelaw_rules_python/rules/py/undefined-export.md +55 -0
  112. codelaw_rules_python/rules/py/undefined-future-feature.md +39 -0
  113. codelaw_rules_python/rules/py/undefined-local.cases.md +80 -0
  114. codelaw_rules_python/rules/py/undefined-local.md +48 -0
  115. codelaw_rules_python/rules/py/undefined-name.cases.md +132 -0
  116. codelaw_rules_python/rules/py/undefined-name.md +42 -0
  117. codelaw_rules_python/rules/py/unintentional-type-annotation.cases.md +13 -0
  118. codelaw_rules_python/rules/py/unintentional-type-annotation.md +38 -0
  119. codelaw_rules_python/rules/py/unreliable-callable-check.md +36 -0
  120. codelaw_rules_python/rules/py/unsorted-imports.cases.md +159 -0
  121. codelaw_rules_python/rules/py/unsorted-imports.md +60 -0
  122. codelaw_rules_python/rules/py/unused-annotation.md +36 -0
  123. codelaw_rules_python/rules/py/unused-import.cases.md +289 -0
  124. codelaw_rules_python/rules/py/unused-import.md +45 -0
  125. codelaw_rules_python/rules/py/unused-loop-control-variable.cases.md +30 -0
  126. codelaw_rules_python/rules/py/unused-loop-control-variable.md +36 -0
  127. codelaw_rules_python/rules/py/unused-variable.cases.md +169 -0
  128. codelaw_rules_python/rules/py/unused-variable.md +38 -0
  129. codelaw_rules_python/rules/py/useless-comparison.md +41 -0
  130. codelaw_rules_python/rules/py/useless-contextlib-suppress.md +44 -0
  131. codelaw_rules_python/rules/py/useless-expression.cases.md +57 -0
  132. codelaw_rules_python/rules/py/useless-expression.md +45 -0
  133. codelaw_rules_python/rules/py/yield-outside-function.md +44 -0
  134. codelaw_rules_python/rules/py/zip-without-explicit-strict.cases.md +26 -0
  135. codelaw_rules_python/rules/py/zip-without-explicit-strict.md +46 -0
  136. codelaw_rules_python-0.2.0.dist-info/METADATA +199 -0
  137. codelaw_rules_python-0.2.0.dist-info/RECORD +139 -0
  138. codelaw_rules_python-0.2.0.dist-info/WHEEL +4 -0
  139. codelaw_rules_python-0.2.0.dist-info/licenses/LICENSE +23 -0
@@ -0,0 +1,40 @@
1
+ # py/no-print — cases
2
+
3
+ ### case: adds the import
4
+
5
+ ```py
6
+ def run():
7
+ print("x")
8
+ ```
9
+
10
+ ```expect
11
+ 2:5 py/no-print
12
+ ```
13
+
14
+ ```fixed
15
+ import logging
16
+ def run():
17
+ logging.getLogger(__name__).info("%s", "x")
18
+ ```
19
+
20
+ ### case: several arguments: no edit
21
+
22
+ ```py
23
+ print("a", 1)
24
+ ```
25
+
26
+ ```expect
27
+ 1:1 py/no-print
28
+ ```
29
+
30
+ ### case: a local print function
31
+
32
+ ```py
33
+ def print(x):
34
+ pass
35
+ print(1)
36
+ ```
37
+
38
+ ```expect
39
+ (none)
40
+ ```
@@ -0,0 +1,42 @@
1
+ ---
2
+ id: py/no-print
3
+ title: "No `print()` in library code"
4
+ languages: [python]
5
+ files: ["src/**"]
6
+ fix: suggest
7
+ fix_reason: "the log level, and whether the output was meant for the user, need a person"
8
+ stability: preview
9
+ since: 0.1.0
10
+ ---
11
+
12
+ Code under `src/` reports through `logging`, not `print()`.
13
+
14
+ **Why:** `print` writes to standard output whether or not the caller wants it; a logger can be filtered, redirected and silenced.
15
+
16
+ ```codelaw
17
+ native: py/no-print
18
+ ```
19
+
20
+ ## Examples
21
+
22
+ ### Python
23
+
24
+ #### Don't
25
+
26
+ ```py path=src/app.py
27
+ import logging
28
+
29
+
30
+ def run():
31
+ print("started")
32
+ ```
33
+
34
+ #### Do
35
+
36
+ ```py path=src/app.py
37
+ import logging
38
+
39
+
40
+ def run():
41
+ logging.getLogger(__name__).info("%s", "started")
42
+ ```
@@ -0,0 +1,11 @@
1
+ # py/no-star-import — cases
2
+
3
+ ### case: relative star import re-exports
4
+
5
+ ```py
6
+ from .core import *
7
+ ```
8
+
9
+ ```expect
10
+ (none)
11
+ ```
@@ -0,0 +1,47 @@
1
+ ---
2
+ id: py/no-star-import
3
+ title: "No `from module import *`"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "which names the module needs can only be found by reading its uses"
7
+ stability: preview
8
+ ported_from: [ruff/F403]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ Modules import the names they use explicitly instead of `from module import *`.
13
+
14
+ **Why:** A star import hides where names come from, can silently replace local names, and stops a linter from finding undefined names.
15
+
16
+ ```codelaw
17
+ native: py/no-star-import
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. List the names the module uses from the star-imported module.
23
+ 2. Replace the star with those names.
24
+
25
+ ## Examples
26
+
27
+ ### Python
28
+
29
+ #### Don't
30
+
31
+ ```py
32
+ from os.path import *
33
+
34
+ print(join("a", "b"))
35
+ ```
36
+
37
+ #### Do
38
+
39
+ ```py
40
+ from os.path import join
41
+
42
+ print(join("a", "b"))
43
+ ```
44
+
45
+ ## Differences from the source
46
+
47
+ Relative star imports (`from .sub import *`) are not reported: that is how a package re-exports its submodules' API, and Ruff leaves them alone in `__init__.py`.
@@ -0,0 +1,54 @@
1
+ # py/no-tunable-literals — cases
2
+
3
+ ### case: default parameter
4
+
5
+ ```py
6
+ def connect(host, retries=5):
7
+ pass
8
+ ```
9
+
10
+ ```expect
11
+ 1:19 py/no-tunable-literals
12
+ ```
13
+
14
+ ### case: module constant is the config point
15
+
16
+ ```py
17
+ TIMEOUT = 30
18
+ ```
19
+
20
+ ```expect
21
+ (none)
22
+ ```
23
+
24
+ ### case: zero and one are fine
25
+
26
+ ```py
27
+ get(url, timeout=0)
28
+ loop(max_items=1)
29
+ ```
30
+
31
+ ```expect
32
+ (none)
33
+ ```
34
+
35
+ ### case: unrelated names
36
+
37
+ ```py
38
+ resize(width=640)
39
+ ```
40
+
41
+ ```expect
42
+ (none)
43
+ ```
44
+
45
+ ### case: local assignment
46
+
47
+ ```py
48
+ def f():
49
+ max_size = 1024
50
+ ```
51
+
52
+ ```expect
53
+ 2:5 py/no-tunable-literals
54
+ ```
@@ -0,0 +1,58 @@
1
+ ---
2
+ id: py/no-tunable-literals
3
+ title: "No hard-coded timeouts, limits or thresholds"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "which configuration key holds the value is the author's choice"
7
+ stability: preview
8
+ since: 0.1.0
9
+ ---
10
+
11
+ A number passed as, defaulted to or assigned to a setting whose name says it is tunable (`timeout`, `retries`, `max_*`, `limit`, `threshold`, `interval`, `delay`, `ttl`, `backoff`, `deadline`, …) comes from configuration, not a literal in the code. `0` and `1` are allowed, and so are module-level `UPPER_CASE` constants, the one place such a value may be written down.
12
+
13
+ **Why:** Tunable values buried in code can't be changed per environment without a release, and the same number drifts apart in several places.
14
+
15
+ ```codelaw
16
+ match:
17
+ any:
18
+ - all:
19
+ - kind: keyword_argument
20
+ - has: { field: name, regex: '(?i)(^|_)(timeouts?|retries|retry|attempts|limit|threshold|interval|delay|ttl|backoff|deadline|max|min|maximum|minimum)(_|$)' }
21
+ - has: { field: value, any: [{ kind: integer }, { kind: float }], not: { regex: '^(0|1|0\.0|1\.0)$' } }
22
+ - all:
23
+ - any: [{ kind: default_parameter }, { kind: typed_default_parameter }]
24
+ - has: { field: name, regex: '(?i)(^|_)(timeouts?|retries|retry|attempts|limit|threshold|interval|delay|ttl|backoff|deadline|max|min|maximum|minimum)(_|$)' }
25
+ - has: { field: value, any: [{ kind: integer }, { kind: float }], not: { regex: '^(0|1|0\.0|1\.0)$' } }
26
+ - all:
27
+ - kind: assignment
28
+ - has: { field: left, kind: identifier, regex: '(?i)(^|_)(timeouts?|retries|retry|attempts|limit|threshold|interval|delay|ttl|backoff|deadline|max|min|maximum|minimum)(_|$)', not: { regex: '^[A-Z0-9_]+$' } }
29
+ - has: { field: right, any: [{ kind: integer }, { kind: float }], not: { regex: '^(0|1|0\.0|1\.0)$' } }
30
+ message: "This tunable value is hard-coded; read it from configuration"
31
+ ```
32
+
33
+ ## How to fix
34
+
35
+ 1. Pick or add a configuration key for the value.
36
+ 2. Read the value from configuration where it is used.
37
+ 3. For a fixed protocol constant, name it as a module-level `UPPER_CASE` constant instead.
38
+
39
+ ## Examples
40
+
41
+ ### Python
42
+
43
+ #### Don't
44
+
45
+ ```py
46
+ def fetch(url):
47
+ return get(url, timeout=30)
48
+ ```
49
+
50
+ #### Do
51
+
52
+ ```py
53
+ from config import settings
54
+
55
+
56
+ def fetch(url):
57
+ return get(url, timeout=settings.http_timeout)
58
+ ```
@@ -0,0 +1,36 @@
1
+ ---
2
+ id: py/none-comparison
3
+ title: "Compare with `None` using `is`"
4
+ languages: [python]
5
+ fix: suggest
6
+ fix_reason: "an object whose `__eq__` treats itself as equal to `None` would compare differently"
7
+ stability: preview
8
+ ported_from: [ruff/E711]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ Comparisons with `None` use `is` / `is not`, not `==` / `!=`.
13
+
14
+ **Why:** `None` is a singleton; `==` calls the other object's `__eq__`, which may not return what you expect.
15
+
16
+ ```codelaw
17
+ native: py/none-comparison
18
+ ```
19
+
20
+ ## Examples
21
+
22
+ ### Python
23
+
24
+ #### Don't
25
+
26
+ ```py
27
+ if user == None:
28
+ login()
29
+ ```
30
+
31
+ #### Do
32
+
33
+ ```py
34
+ if user is None:
35
+ login()
36
+ ```
@@ -0,0 +1,35 @@
1
+ ---
2
+ id: py/not-in-test
3
+ title: "Write `x not in y`"
4
+ languages: [python]
5
+ fix: auto
6
+ stability: preview
7
+ ported_from: [ruff/E713]
8
+ since: 0.1.0
9
+ ---
10
+
11
+ A membership test is negated as `x not in y`, not `not x in y`.
12
+
13
+ **Why:** `not x in y` reads as `(not x) in y`; `not in` says what is meant.
14
+
15
+ ```codelaw
16
+ native: py/not-in-test
17
+ ```
18
+
19
+ ## Examples
20
+
21
+ ### Python
22
+
23
+ #### Don't
24
+
25
+ ```py
26
+ if not item in seen:
27
+ keep(item)
28
+ ```
29
+
30
+ #### Do
31
+
32
+ ```py
33
+ if item not in seen:
34
+ keep(item)
35
+ ```
@@ -0,0 +1,35 @@
1
+ ---
2
+ id: py/not-is-test
3
+ title: "Write `x is not y`"
4
+ languages: [python]
5
+ fix: auto
6
+ stability: preview
7
+ ported_from: [ruff/E714]
8
+ since: 0.1.0
9
+ ---
10
+
11
+ An identity test is negated as `x is not y`, not `not x is y`.
12
+
13
+ **Why:** `not x is y` reads as `(not x) is y`; `is not` says what is meant.
14
+
15
+ ```codelaw
16
+ native: py/not-is-test
17
+ ```
18
+
19
+ ## Examples
20
+
21
+ ### Python
22
+
23
+ #### Don't
24
+
25
+ ```py
26
+ if not value is None:
27
+ use(value)
28
+ ```
29
+
30
+ #### Do
31
+
32
+ ```py
33
+ if value is not None:
34
+ use(value)
35
+ ```
@@ -0,0 +1,38 @@
1
+ ---
2
+ id: py/percent-format-count-mismatch
3
+ title: "A `%` format string gets one value per placeholder"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "which value is missing, or extra, can't be inferred"
7
+ stability: preview
8
+ ported_from: [ruff/F507]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ The tuple (or single literal) formatted into a `%` string has as many values as the string has positional placeholders.
13
+
14
+ **Why:** Too few values raise `TypeError: not enough arguments`; too many raise `TypeError: not all arguments converted`.
15
+
16
+ ```codelaw
17
+ native: py/percent-format-count-mismatch
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Add or remove values, or placeholders, until they match.
23
+
24
+ ## Examples
25
+
26
+ ### Python
27
+
28
+ #### Don't
29
+
30
+ ```py
31
+ message = "%s is %s" % ("Ada",)
32
+ ```
33
+
34
+ #### Do
35
+
36
+ ```py
37
+ message = "%s is %s" % ("Ada", 36)
38
+ ```
@@ -0,0 +1,38 @@
1
+ ---
2
+ id: py/percent-format-expected-mapping
3
+ title: "Named `%` placeholders get a mapping"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "the key each value belongs to isn't written anywhere"
7
+ stability: preview
8
+ ported_from: [ruff/F502]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ A `%` format string with named placeholders (`%(name)s`) is applied to a mapping, not a tuple.
13
+
14
+ **Why:** Python raises `TypeError: format requires a mapping`.
15
+
16
+ ```codelaw
17
+ native: py/percent-format-expected-mapping
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Pass a dict whose keys are the placeholder names.
23
+
24
+ ## Examples
25
+
26
+ ### Python
27
+
28
+ #### Don't
29
+
30
+ ```py
31
+ message = "%(name)s" % ("Ada",)
32
+ ```
33
+
34
+ #### Do
35
+
36
+ ```py
37
+ message = "%(name)s" % {"name": "Ada"}
38
+ ```
@@ -0,0 +1,38 @@
1
+ ---
2
+ id: py/percent-format-expected-sequence
3
+ title: "Positional `%` placeholders get a tuple"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "the order of the values isn't written anywhere"
7
+ stability: preview
8
+ ported_from: [ruff/F503]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ A `%` format string with two or more positional placeholders is applied to a tuple, not a dict.
13
+
14
+ **Why:** A dict is a single value, so Python raises `TypeError: not enough arguments`.
15
+
16
+ ```codelaw
17
+ native: py/percent-format-expected-sequence
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Pass a tuple with one value per placeholder, or switch to named placeholders.
23
+
24
+ ## Examples
25
+
26
+ ### Python
27
+
28
+ #### Don't
29
+
30
+ ```py
31
+ message = "%s and %s" % {"a": 1, "b": 2}
32
+ ```
33
+
34
+ #### Do
35
+
36
+ ```py
37
+ message = "%s and %s" % (1, 2)
38
+ ```
@@ -0,0 +1,38 @@
1
+ ---
2
+ id: py/percent-format-invalid
3
+ title: "A `%` format string is complete"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "the intended placeholder can't be inferred"
7
+ stability: preview
8
+ ported_from: [ruff/F501]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ A string formatted with `%` has no unfinished placeholder such as `%(name` or a trailing `%`.
13
+
14
+ **Why:** Python raises `ValueError` when the string is formatted.
15
+
16
+ ```codelaw
17
+ native: py/percent-format-invalid
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Finish the placeholder (`%(name)s`) or escape a literal percent as `%%`.
23
+
24
+ ## Examples
25
+
26
+ ### Python
27
+
28
+ #### Don't
29
+
30
+ ```py
31
+ message = "%(name" % {"name": "Ada"}
32
+ ```
33
+
34
+ #### Do
35
+
36
+ ```py
37
+ message = "%(name)s" % {"name": "Ada"}
38
+ ```
@@ -0,0 +1,38 @@
1
+ ---
2
+ id: py/percent-format-missing-argument
3
+ title: "Every named `%` placeholder has a value"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "the value for the missing key has to be supplied"
7
+ stability: preview
8
+ ported_from: [ruff/F505]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ A dict literal formatted into a `%` string has a key for every named placeholder.
13
+
14
+ **Why:** A missing key raises `KeyError` when the string is formatted.
15
+
16
+ ```codelaw
17
+ native: py/percent-format-missing-argument
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Add the missing key to the dict, or remove the placeholder.
23
+
24
+ ## Examples
25
+
26
+ ### Python
27
+
28
+ #### Don't
29
+
30
+ ```py
31
+ message = "%(name)s is %(age)s" % {"name": "Ada"}
32
+ ```
33
+
34
+ #### Do
35
+
36
+ ```py
37
+ message = "%(name)s is %(age)s" % {"name": "Ada", "age": 36}
38
+ ```
@@ -0,0 +1,38 @@
1
+ ---
2
+ id: py/percent-format-mixed-placeholders
3
+ title: "A `%` format string doesn't mix positional and named placeholders"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "which style the string should use is the author's choice"
7
+ stability: preview
8
+ ported_from: [ruff/F506]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ A `%` format string uses either positional placeholders (`%s`) or named ones (`%(name)s`), not both.
13
+
14
+ **Why:** Python can't fill both from one right-hand side and raises `TypeError`.
15
+
16
+ ```codelaw
17
+ native: py/percent-format-mixed-placeholders
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Name every placeholder, or make every placeholder positional.
23
+
24
+ ## Examples
25
+
26
+ ### Python
27
+
28
+ #### Don't
29
+
30
+ ```py
31
+ message = "%(name)s is %s" % {"name": "Ada"}
32
+ ```
33
+
34
+ #### Do
35
+
36
+ ```py
37
+ message = "%(name)s is %(age)s" % {"name": "Ada", "age": 36}
38
+ ```
@@ -0,0 +1,38 @@
1
+ ---
2
+ id: py/percent-format-star-requires-sequence
3
+ title: "A `*` width in a `%` format takes a tuple"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "the order of the values has to be decided"
7
+ stability: preview
8
+ ported_from: [ruff/F508]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ A `%` format string using `*` for a width or precision is applied to a tuple.
13
+
14
+ **Why:** The `*` takes its value from the next positional argument, which a mapping doesn't have.
15
+
16
+ ```codelaw
17
+ native: py/percent-format-star-requires-sequence
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Pass a tuple with the width before the value.
23
+
24
+ ## Examples
25
+
26
+ ### Python
27
+
28
+ #### Don't
29
+
30
+ ```py
31
+ message = "%*d" % {"width": 5, "value": 1}
32
+ ```
33
+
34
+ #### Do
35
+
36
+ ```py
37
+ message = "%*d" % (5, 1)
38
+ ```
@@ -0,0 +1,38 @@
1
+ ---
2
+ id: py/percent-format-unsupported-character
3
+ title: "A `%` format uses known conversions"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "the intended conversion can't be inferred"
7
+ stability: preview
8
+ ported_from: [ruff/F509]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ Each `%` placeholder ends with a conversion Python knows (`s`, `r`, `a`, `d`, `f`, `x`, …).
13
+
14
+ **Why:** An unknown conversion raises `ValueError: unsupported format character`.
15
+
16
+ ```codelaw
17
+ native: py/percent-format-unsupported-character
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Use the conversion you meant, or escape a literal percent as `%%`.
23
+
24
+ ## Examples
25
+
26
+ ### Python
27
+
28
+ #### Don't
29
+
30
+ ```py
31
+ message = "%y" % (1,)
32
+ ```
33
+
34
+ #### Do
35
+
36
+ ```py
37
+ message = "%s" % (1,)
38
+ ```