sarj-python-lint 0.81.7__tar.gz → 0.81.9__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 (127) hide show
  1. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/PKG-INFO +1 -1
  2. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/pyproject.toml +1 -1
  3. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +23 -7
  4. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +5 -6
  5. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/.gitignore +0 -0
  6. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/LICENSE +0 -0
  7. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/README.md +0 -0
  8. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/__init__.py +0 -0
  9. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/__main__.py +0 -0
  10. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/_analysis_session.py +0 -0
  11. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/_filesystem.py +0 -0
  12. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/_python_target.py +0 -0
  13. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  14. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/_secret_names.py +0 -0
  15. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/_version.py +0 -0
  16. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/py.typed +0 -0
  17. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/ratchet.py +0 -0
  18. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rule_base.py +0 -0
  19. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/__init__.py +0 -0
  20. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  21. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_comments.py +0 -0
  22. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  23. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  24. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_first_party.py +0 -0
  25. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  26. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_imports.py +0 -0
  27. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_logging.py +0 -0
  28. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_paths.py +0 -0
  29. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_project_index.py +0 -0
  30. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  31. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_pytest.py +0 -0
  32. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_registry.py +0 -0
  33. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_sql.py +0 -0
  34. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  35. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  36. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  37. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  38. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  39. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  40. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  41. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  42. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  43. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  44. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  45. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  46. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  47. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  48. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  49. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  50. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  51. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  52. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  53. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  54. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  55. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  56. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  57. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  58. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  59. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  60. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  61. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  62. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  63. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  64. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  65. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  66. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  67. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  68. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  69. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  70. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  71. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  72. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  73. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  74. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  75. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  76. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  77. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  78. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  79. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  80. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  81. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  82. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  83. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  84. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  85. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  86. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  87. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  88. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  89. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  90. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  91. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  92. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  93. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  94. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  95. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  96. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  97. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  98. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  99. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  100. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  101. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  102. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  103. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  104. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  105. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  106. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  107. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
  108. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  109. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  110. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  111. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  112. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  113. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  114. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  115. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  116. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  117. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  118. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  119. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  120. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/stepdown.py +0 -0
  121. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  122. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  123. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  124. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  125. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  126. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  127. {sarj_python_lint-0.81.7 → sarj_python_lint-0.81.9}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 0.81.7
3
+ Version: 0.81.9
4
4
  Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
5
5
  Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
6
6
  Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sarj-python-lint"
3
- version = "0.81.7"
3
+ version = "0.81.9"
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 @@ class NoRawConnectionInTests(Rule):
42
42
  category=RuleCategory.TESTING,
43
43
  autofix=AutofixPolicy.NONE,
44
44
  limitations=(
45
- "Only test paths outside conftest.py and conventional shared test-support modules are inspected.",
45
+ "Only collected test modules outside conftest.py, conventional shared test-support modules, and migration-test trees are inspected.",
46
46
  "A receiver is reported only when a parameter, annotated local, or constructor call proves it is a psycopg ConnectionPool or AsyncConnectionPool.",
47
47
  "Pytest fixtures may use a connection internally for setup and cleanup, but fixtures that return or yield the connection remain reportable.",
48
48
  ),
@@ -81,12 +81,16 @@ class NoRawConnectionInTests(Rule):
81
81
 
82
82
  @override
83
83
  def check(self, path: Path, source: str) -> list[Diagnostic]:
84
- if (
85
- not is_test_path(path)
86
- or path.name == "conftest.py"
87
- or is_test_support_path(path)
88
- or is_generated(path, source)
89
- ):
84
+ excluded_path = any(
85
+ (
86
+ not is_test_path(path),
87
+ path.name == "conftest.py",
88
+ is_test_support_path(path),
89
+ _is_non_collected_test_support(path),
90
+ _is_migration_test(path),
91
+ )
92
+ )
93
+ if excluded_path or is_generated(path, source):
90
94
  return []
91
95
  tree = parse_or_none(path, source)
92
96
  if tree is None:
@@ -120,6 +124,18 @@ class NoRawConnectionInTests(Rule):
120
124
  return sorted(diagnostics, key=lambda item: (item.line, item.col))
121
125
 
122
126
 
127
+ def _is_non_collected_test_support(path: Path) -> bool:
128
+ return "tests" in path.parts and not (path.name.startswith("test_") or path.name.endswith("_test.py"))
129
+
130
+
131
+ def _is_migration_test(path: Path) -> bool:
132
+ parts = path.parts
133
+ return any(
134
+ part == "tests" and index + 1 < len(parts) and parts[index + 1] == "migrations"
135
+ for index, part in enumerate(parts)
136
+ )
137
+
138
+
123
139
  def _proven_pool_names(scope: ast.Module | ast.FunctionDef | ast.AsyncFunctionDef) -> frozenset[str]:
124
140
  names: set[str] = set()
125
141
  if isinstance(scope, (ast.FunctionDef, ast.AsyncFunctionDef)):
@@ -103,7 +103,7 @@ class PreferMatchTypeDispatch(Rule):
103
103
  category=RuleCategory.MAINTAINABILITY,
104
104
  autofix=AutofixPolicy.NONE,
105
105
  limitations=(
106
- "General dispatch requires three or more adjacent, unguarded `isinstance` branches over the same simple name. Two arms are checked only for an exact `ast.Name.id` / `ast.Attribute.attr` projection followed by `return None`.",
106
+ "General dispatch requires three or more adjacent, unguarded `isinstance` branches over the same simple name. Two arms are checked only for an exact `ast.Name.id` / `ast.Attribute.attr` projection followed by a None-returning fallback.",
107
107
  "Nested-guard findings require exactly two negated checks joined by `or`: an imported or module-local class for a simple name, followed by an imported or module-local class for an attribute rooted at that name, with an unconditional built-in `TypeError` raise.",
108
108
  "The checked types must be unshadowed builtins, unshadowed module-local classes, or proven stdlib ast classes; unresolved imports, runtime type groups, repeated type references, generated files, and non-terminating sibling checks are excluded.",
109
109
  "A terminal-looking context-manager body does not prove a sibling branch terminates: exceptions can be suppressed. An unconditional return or raise after the context manager remains eligible.",
@@ -597,10 +597,8 @@ def _is_preceding_type_branch(
597
597
 
598
598
 
599
599
  def _returns_none(statement: ast.stmt) -> bool:
600
- return (
601
- isinstance(statement, ast.Return)
602
- and isinstance(statement.value, ast.Constant)
603
- and statement.value.value is None
600
+ return isinstance(statement, ast.Return) and (
601
+ statement.value is None or (isinstance(statement.value, ast.Constant) and statement.value.value is None)
604
602
  )
605
603
 
606
604
 
@@ -760,6 +758,7 @@ def _statement_blocks(node: ast.AST) -> tuple[list[ast.stmt], ...]:
760
758
  case ast.With() | ast.AsyncWith() | ast.ExceptHandler() | ast.match_case():
761
759
  return (node.body,)
762
760
  case ast.Try() | ast.TryStar():
763
- return node.body, node.orelse, node.finalbody, *(handler.body for handler in node.handlers)
761
+ # ExceptHandler owns each handler body when ast.walk reaches it.
762
+ return node.body, node.orelse, node.finalbody
764
763
  case _:
765
764
  return ()