sarj-python-lint 0.37.0__tar.gz → 0.38.0__tar.gz

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 (94) hide show
  1. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/_secret_names.py +9 -2
  4. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/_logging.py +23 -0
  5. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_fstring_in_log.py +2 -18
  6. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_secret_in_log.py +2 -5
  7. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_sentinel_return_on_except.py +2 -15
  8. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +5 -0
  9. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +2 -17
  10. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/.gitignore +0 -0
  11. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/README.md +0 -0
  12. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/__init__.py +0 -0
  13. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/__main__.py +0 -0
  14. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  15. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/_version.py +0 -0
  16. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/py.typed +0 -0
  17. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/ratchet.py +0 -0
  18. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rule_base.py +0 -0
  19. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  20. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  21. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  22. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  23. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  24. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  25. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  26. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/_registry.py +0 -0
  27. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  28. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  29. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/conditional_assertion_in_test.py +0 -0
  30. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  31. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  32. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/duplicate_test_body.py +0 -0
  33. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/duplicated_override_docstring.py +0 -0
  34. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/fixture_returns_bare_tuple.py +0 -0
  35. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/inefficient_string_concat_in_loop.py +0 -0
  36. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/interaction_only_test.py +0 -0
  37. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/kwarg_heavy_construction_in_test.py +0 -0
  38. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/kwonly_same_type_params.py +0 -0
  39. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  40. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_aggregation_in_store_query.py +0 -0
  41. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  42. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  43. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_fat_try_blocks.py +0 -0
  44. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_noqa.py +0 -0
  45. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_file_level_suppression.py +0 -0
  46. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  47. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_gen_random_uuid_in_sql.py +0 -0
  48. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_isinstance_union_chain.py +0 -0
  49. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  50. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_optional_tenant_predicate.py +0 -0
  51. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_query_with_many_joins.py +0 -0
  52. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_raw_sql_in_tests.py +0 -0
  53. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  54. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  55. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  56. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_sequential_await.py +0 -0
  57. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_sleep_in_test_body.py +0 -0
  58. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_stdlib_logging.py +0 -0
  59. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  60. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/no_unreachable_after_terminal.py +0 -0
  61. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  62. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/parametrize_case_needs_id.py +0 -0
  63. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  64. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  65. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  66. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_match_pattern_destructuring.py +0 -0
  67. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  68. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  69. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  70. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  71. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  72. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_real_store_in_tests.py +0 -0
  73. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  74. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  75. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  76. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_timedelta_for_durations.py +0 -0
  77. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  78. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  79. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  80. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
  81. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  82. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  83. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  84. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/single_public_export.py +0 -0
  85. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/sleep_with_computed_arg_in_test.py +0 -0
  86. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  87. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  88. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/tautological_mock_assertion.py +0 -0
  89. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/test_loops_over_literal_cases.py +0 -0
  90. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  91. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/trivially_true_assertion.py +0 -0
  92. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  93. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/xfail_requires_strict.py +0 -0
  94. {sarj_python_lint-0.37.0 → sarj_python_lint-0.38.0}/src/sarj_python_lint/rules/zero_assertion_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sarj-python-lint
3
- Version: 0.37.0
3
+ Version: 0.38.0
4
4
  Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
5
5
  Project-URL: Homepage, https://github.com/sarj-ai/standards/tree/main/packages/python
6
6
  Project-URL: Repository, https://github.com/sarj-ai/standards
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sarj-python-lint"
3
- version = "0.37.0"
3
+ version = "0.38.0"
4
4
  description = "Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "sarj-ai" }]
@@ -42,7 +42,7 @@ from itertools import pairwise
42
42
  import re
43
43
 
44
44
 
45
- _SECRET_WORDS = frozenset(
45
+ SECRET_WORDS = frozenset(
46
46
  {
47
47
  "token",
48
48
  "secret",
@@ -59,9 +59,16 @@ _SECRET_WORDS = frozenset(
59
59
  "digest",
60
60
  "hash",
61
61
  "apikey",
62
+ # `bearer` is in the TypeScript twin's SECRET_WORDS and was in neither
63
+ # Python set, so `bearer == provided` was a flagged timing attack in one
64
+ # engine and silent in the other. The lists are otherwise identical,
65
+ # which is exactly what made the gap invisible.
66
+ "bearer",
62
67
  }
63
68
  )
64
69
 
70
+ _SECRET_WORDS = SECRET_WORDS
71
+
65
72
  # Tokens that mark a counter, row-id, feature flag, or boolean presence/state
66
73
  # marker. As the TRAILING token they mean the identifier is metadata *about* a
67
74
  # secret, not the secret itself, so it is not a leak / timing surface even when a
@@ -165,7 +172,7 @@ def is_secret_name(identifier: str) -> bool:
165
172
  return False
166
173
  if leading_word(identifier) in _FLAG_PREFIXES:
167
174
  return False
168
- if any(tok in _SECRET_WORDS for tok in tokens):
175
+ if any(tok in SECRET_WORDS for tok in tokens):
169
176
  return True
170
177
  return _has_api_key(tokens)
171
178
 
@@ -17,6 +17,29 @@ _LOGGER_NAMES = frozenset({"logger", "log", "logging", "loguru", "_logger", "_lo
17
17
  LOGGER_FACTORIES = frozenset({"getlogger", "get_logger"})
18
18
  _LOGGER_FACTORIES = LOGGER_FACTORIES
19
19
 
20
+ # Public: the `.attr` of a logging call. SARJ012 (secret-in-log) once carried a
21
+ # SHORTER list than SARJ017 (f-string-in-log) -- 7 names against 11 -- so
22
+ # `logger.success("auth", token=token)` was a log call to one rule and not to
23
+ # the other, and the SECURITY rule was the one that could not see it. `success`,
24
+ # `trace` and `log` are documented loguru levels and `loguru` is already in
25
+ # `_LOGGER_NAMES`, so the omission was a silent false negative on a real idiom.
26
+ # One set, imported everywhere, is what stops that recurring.
27
+ LOG_METHODS = frozenset(
28
+ {
29
+ "critical",
30
+ "debug",
31
+ "error",
32
+ "exception",
33
+ "fatal",
34
+ "info",
35
+ "log",
36
+ "success",
37
+ "trace",
38
+ "warn",
39
+ "warning",
40
+ }
41
+ )
42
+
20
43
 
21
44
  def is_logger_expr(expr: ast.expr) -> bool:
22
45
  """Report whether `expr` evaluates to a logger.
@@ -11,29 +11,13 @@ from typing import TYPE_CHECKING, override
11
11
 
12
12
  from sarj_python_lint.rule_base import Diagnostic, Rule, parse_or_none
13
13
  from sarj_python_lint.rules._ast_index import nodes
14
- from sarj_python_lint.rules._logging import LOGGER_FACTORIES, is_logger_expr
14
+ from sarj_python_lint.rules._logging import LOG_METHODS, LOGGER_FACTORIES, is_logger_expr
15
15
 
16
16
 
17
17
  if TYPE_CHECKING:
18
18
  from pathlib import Path
19
19
 
20
20
 
21
- _LOG_METHODS = frozenset(
22
- {
23
- "debug",
24
- "info",
25
- "warning",
26
- "warn",
27
- "error",
28
- "exception",
29
- "critical",
30
- "fatal",
31
- "trace",
32
- "success",
33
- "log",
34
- }
35
- )
36
-
37
21
  # Keyword arguments defined by stdlib `logging` (and never structured fields).
38
22
  # Their presence marks the call as a stdlib logger, for which the loguru-style
39
23
  # structured-keyword rewrite is wrong.
@@ -97,7 +81,7 @@ def _candidates(tree: ast.Module) -> list[tuple[ast.Call, ast.JoinedStr]]:
97
81
 
98
82
  def _is_logging_call(node: ast.Call) -> bool:
99
83
  func = node.func
100
- if not isinstance(func, ast.Attribute) or func.attr not in _LOG_METHODS:
84
+ if not isinstance(func, ast.Attribute) or func.attr not in LOG_METHODS:
101
85
  return False
102
86
  return is_logger_expr(func.value)
103
87
 
@@ -13,16 +13,13 @@ from typing import TYPE_CHECKING, override
13
13
  from sarj_python_lint._secret_names import identifier_tokens, is_secret_name
14
14
  from sarj_python_lint.rule_base import Diagnostic, Rule, parse_or_none
15
15
  from sarj_python_lint.rules._ast_index import nodes
16
- from sarj_python_lint.rules._logging import is_logger_expr
16
+ from sarj_python_lint.rules._logging import LOG_METHODS, is_logger_expr
17
17
 
18
18
 
19
19
  if TYPE_CHECKING:
20
20
  from pathlib import Path
21
21
 
22
22
 
23
- # Logging method names (the `.attr` of the call's func).
24
- _LOG_METHODS = frozenset({"debug", "info", "warning", "warn", "error", "exception", "critical"})
25
-
26
23
  # A redaction marker (`token_prefix`, `password_hash`, `secret_masked`,
27
24
  # `api_key_tag`) means the keyword carries a masked/derived value, not the raw
28
25
  # secret — the intended safe form.
@@ -88,6 +85,6 @@ def _is_logging_call(node: ast.Call) -> bool:
88
85
  func = node.func
89
86
  if not isinstance(func, ast.Attribute):
90
87
  return False
91
- if func.attr not in _LOG_METHODS:
88
+ if func.attr not in LOG_METHODS:
92
89
  return False
93
90
  return is_logger_expr(func.value)
@@ -12,6 +12,7 @@ from typing import TYPE_CHECKING, override
12
12
 
13
13
  from sarj_python_lint.rule_base import Diagnostic, Rule, parse_or_none
14
14
  from sarj_python_lint.rules._ast_index import children, nodes, walk
15
+ from sarj_python_lint.rules._logging import LOG_METHODS
15
16
 
16
17
 
17
18
  if TYPE_CHECKING:
@@ -375,20 +376,6 @@ def _is_sentinel(value: ast.expr) -> bool:
375
376
  return False
376
377
 
377
378
 
378
- _LOG_METHODS: frozenset[str] = frozenset(
379
- {
380
- "debug",
381
- "info",
382
- "warning",
383
- "warn",
384
- "error",
385
- "exception",
386
- "critical",
387
- "fatal",
388
- }
389
- )
390
-
391
-
392
379
  def _handler_logs_before_return(handler: ast.ExceptHandler) -> bool:
393
380
  """Report whether some logging call can reach the handler's final sentinel return."""
394
381
  _, logged_fallthrough = _list_props(handler.body[:-1])
@@ -502,7 +489,7 @@ def _is_logging_call(node: ast.AST) -> bool:
502
489
  if not isinstance(node, ast.Call):
503
490
  return False
504
491
  func = node.func
505
- if not isinstance(func, ast.Attribute) or func.attr not in _LOG_METHODS:
492
+ if not isinstance(func, ast.Attribute) or func.attr not in LOG_METHODS:
506
493
  return False
507
494
  return _is_logger_receiver(func.value)
508
495
 
@@ -57,6 +57,11 @@ _AUTH_WORDS = frozenset(
57
57
  "signature",
58
58
  "hmac",
59
59
  "apikey",
60
+ # `bearer` was in the TypeScript twin's AUTH_WORDS and in neither Python
61
+ # list, so `bearer == provided` was a flagged timing attack in TS and a
62
+ # silent one here. The two lists are otherwise identical, which is what
63
+ # made the gap invisible.
64
+ "bearer",
60
65
  }
61
66
  )
62
67
 
@@ -14,7 +14,7 @@ from typing import TYPE_CHECKING, override
14
14
 
15
15
  from sarj_python_lint.rule_base import Diagnostic, Rule, parse_or_none
16
16
  from sarj_python_lint.rules._ast_index import nodes, walk
17
- from sarj_python_lint.rules._logging import is_logger_expr
17
+ from sarj_python_lint.rules._logging import LOG_METHODS, is_logger_expr
18
18
  from sarj_python_lint.rules._paths import is_generated
19
19
 
20
20
 
@@ -24,21 +24,6 @@ if TYPE_CHECKING:
24
24
 
25
25
  # Method names that make a logger-receiver call a logging call. Kept in sync by
26
26
  # construction with SARJ017's list; the receiver test is the shared helper.
27
- _LOG_METHODS = frozenset(
28
- {
29
- "debug",
30
- "info",
31
- "warning",
32
- "warn",
33
- "error",
34
- "exception",
35
- "critical",
36
- "fatal",
37
- "trace",
38
- "success",
39
- "log",
40
- }
41
- )
42
27
 
43
28
  # A literal ENDING in an UPPERCASE SQL keyword is a query fragment awaiting
44
29
  # interpolation — ruff's S608 and SARJ021 own that shape. Anchored at the end,
@@ -174,7 +159,7 @@ def _comment_lines(source: str) -> frozenset[int]:
174
159
  def _is_logging_call(node: ast.Call) -> bool:
175
160
  """Report whether `node` is a logging call, using SARJ017's receiver resolver."""
176
161
  func = node.func
177
- if not isinstance(func, ast.Attribute) or func.attr not in _LOG_METHODS:
162
+ if not isinstance(func, ast.Attribute) or func.attr not in LOG_METHODS:
178
163
  return False
179
164
  return is_logger_expr(func.value)
180
165