sarj-python-lint 1.83.0__tar.gz → 1.84.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 (291) hide show
  1. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/__main__.py +3 -2
  4. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_autofix.py +7 -4
  5. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_autofix_enums.py +1 -1
  6. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_autofix_repeated.py +1 -1
  7. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_file_context.py +11 -1
  8. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_annotation_origins.py +43 -0
  9. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_annotation_semantics.py +7 -4
  10. sarj_python_lint-1.84.0/src/sarj_python_lint/rules/_binding_inventory.py +131 -0
  11. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_closed_helpers.py +1 -1
  12. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_copy_on_write.py +29 -20
  13. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +3 -1
  14. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_docstrings.py +7 -4
  15. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_fastapi.py +7 -4
  16. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_imports.py +7 -4
  17. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +18 -12
  18. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_native_object_contracts.py +25 -16
  19. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_opaque_contracts.py +22 -15
  20. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_project_index.py +72 -60
  21. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_prose_budget.py +7 -4
  22. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_registry.py +6 -0
  23. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_resource_provenance.py +20 -87
  24. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_store_writes.py +8 -5
  25. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_test_composition.py +17 -14
  26. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +8 -5
  27. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +7 -4
  28. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +14 -8
  29. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +7 -4
  30. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +7 -4
  31. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +48 -37
  32. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +39 -22
  33. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +7 -4
  34. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +16 -10
  35. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +7 -4
  36. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +15 -9
  37. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +8 -5
  38. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +8 -7
  39. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +18 -8
  40. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_tautological_expect.py +29 -17
  41. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +23 -20
  42. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/over_mocked_test.py +7 -6
  43. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_class_row.py +14 -10
  44. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +7 -4
  45. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +17 -11
  46. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +14 -8
  47. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +7 -4
  48. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +7 -4
  49. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +7 -4
  50. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_library_fake.py +7 -4
  51. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +151 -7
  52. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +9 -4
  53. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +14 -8
  54. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +14 -11
  55. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +7 -4
  56. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +13 -10
  57. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +10 -8
  58. sarj_python_lint-1.84.0/src/sarj_python_lint/rules/prefer_runtime_validation.py +97 -0
  59. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +7 -4
  60. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +7 -4
  61. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_str_enum.py +45 -30
  62. sarj_python_lint-1.84.0/src/sarj_python_lint/rules/prefer_typed_cli.py +219 -0
  63. sarj_python_lint-1.84.0/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +189 -0
  64. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +9 -6
  65. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +22 -19
  66. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/redundant_docstring.py +8 -7
  67. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +8 -5
  68. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +7 -4
  69. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +30 -21
  70. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/require_port_for_service.py +23 -16
  71. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +16 -10
  72. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +18 -20
  73. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +8 -5
  74. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/restated_test_docstring.py +16 -12
  75. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/stepdown.py +8 -5
  76. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +29 -17
  77. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +7 -4
  78. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +30 -24
  79. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/unused_mock_setup.py +19 -5
  80. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/.gitignore +0 -0
  81. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/LICENSE +0 -0
  82. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/README.md +0 -0
  83. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/__init__.py +0 -0
  84. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  85. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
  86. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
  87. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_autofix_comments.py +0 -0
  88. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
  89. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
  90. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_autofix_docs.py +0 -0
  91. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
  92. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_autofix_inert.py +0 -0
  93. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
  94. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_autofix_security.py +0 -0
  95. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
  96. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
  97. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_filesystem.py +0 -0
  98. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_fixes.py +0 -0
  99. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_parallel.py +0 -0
  100. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_python_target.py +0 -0
  101. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  102. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_secret_names.py +0 -0
  103. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_source.py +0 -0
  104. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/_version.py +0 -0
  105. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/application_logging.py +0 -0
  106. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/autofix.py +0 -0
  107. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/benchmark.py +0 -0
  108. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/cast_access.py +0 -0
  109. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  110. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/json_boundary.py +0 -0
  111. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/py.typed +0 -0
  112. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/ratchet.py +0 -0
  113. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rule_base.py +0 -0
  114. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  115. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  116. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
  117. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  118. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_async_lifecycle.py +0 -0
  119. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
  120. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  121. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  122. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
  123. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  124. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  125. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  126. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  127. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  128. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  129. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
  130. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  131. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  132. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  133. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  134. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  135. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_numeric_proof.py +0 -0
  136. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  137. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  138. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  139. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  140. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  141. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  142. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
  143. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  144. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  145. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
  146. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
  147. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
  148. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  149. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  150. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  151. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  152. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
  153. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  154. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  155. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/async_executor_shutdown.py +0 -0
  156. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  157. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +0 -0
  158. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +0 -0
  159. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  160. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  161. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  162. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
  163. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  164. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +0 -0
  165. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  166. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  167. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  168. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  169. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  170. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  171. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  172. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +0 -0
  173. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  174. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  175. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  176. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  177. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  178. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  179. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  180. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  181. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  182. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  183. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  184. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  185. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  186. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  187. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  188. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  189. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  190. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  191. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  192. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  193. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  194. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  195. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  196. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  197. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  198. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  199. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  200. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  201. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  202. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  203. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  204. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  205. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  206. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  207. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  208. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  209. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  210. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  211. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  212. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  213. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  214. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  215. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  216. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  217. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  218. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  219. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  220. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  221. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  222. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  223. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  224. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  225. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  226. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  227. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  228. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  229. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  230. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  231. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  232. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  233. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  234. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  235. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  236. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  237. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  238. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  239. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  240. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  241. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  242. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  243. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  244. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  245. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
  246. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
  247. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  248. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  249. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  250. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  251. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  252. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  253. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  254. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  255. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
  256. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  257. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  258. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
  259. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  260. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  261. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  262. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  263. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
  264. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  265. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  266. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  267. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  268. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
  269. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  270. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  271. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  272. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  273. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  274. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  275. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  276. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  277. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  278. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  279. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  280. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  281. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  282. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  283. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  284. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  285. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  286. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  287. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  288. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  289. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  290. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
  291. {sarj_python_lint-1.83.0 → sarj_python_lint-1.84.0}/src/sarj_python_lint/suppression_policy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 1.83.0
3
+ Version: 1.84.0
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 = "1.83.0"
3
+ version = "1.84.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" }]
@@ -187,12 +187,13 @@ _DIAGNOSTIC_PRECEDENCE = MappingProxyType(
187
187
  # A proven external or nested request boundary already requires schema
188
188
  # validation at this field read. Retain standalone shape advice when
189
189
  # that more specific rule is unselected or locally suppressed.
190
- "SARJ411": frozenset({"SARJ521"}),
190
+ "SARJ411": frozenset({"SARJ521", "SARJ535"}),
191
191
  # Any-valued mappings are the stronger correctness failure. SARJ008
192
192
  # still owns every fixed dictionary return when selected alone, while
193
193
  # an all-rules run emits one actionable diagnostic for this overlap.
194
194
  "SARJ447": frozenset({"SARJ008"}),
195
- "SARJ450": frozenset({"SARJ521"}),
195
+ "SARJ450": frozenset({"SARJ521", "SARJ535"}),
196
+ "SARJ521": frozenset({"SARJ535"}),
196
197
  "SARJ457": frozenset({"SARJ045"}),
197
198
  "SARJ465": frozenset({"SARJ071"}),
198
199
  "SARJ491": frozenset({"SARJ400"}),
@@ -993,10 +993,13 @@ def _jwt_required_sequence(required: ast.expr) -> ast.expr | None:
993
993
  ):
994
994
  return None
995
995
  elements = [*required.elts, ast.Constant(value="exp")]
996
- if isinstance(required, ast.List):
997
- return ast.List(elts=elements, ctx=ast.Load())
998
- if isinstance(required, ast.Tuple):
999
- return ast.Tuple(elts=elements, ctx=ast.Load())
996
+ match required:
997
+ case ast.List():
998
+ return ast.List(elts=elements, ctx=ast.Load())
999
+ case ast.Tuple():
1000
+ return ast.Tuple(elts=elements, ctx=ast.Load())
1001
+ case _:
1002
+ pass
1000
1003
  return ast.Set(elts=elements)
1001
1004
 
1002
1005
 
@@ -164,7 +164,7 @@ class EnumFixes:
164
164
 
165
165
  def _stable_alias(self, alias: ast.TypeAlias) -> bool:
166
166
  if any(
167
- bindings.get(alias.name.id, []) not in ([], [alias.name]) for bindings in self.provenance.bindings.values()
167
+ bindings.get(alias.name.id, ()) not in {(), (alias.name,)} for bindings in self.provenance.bindings.values()
168
168
  ):
169
169
  return False
170
170
  if any(
@@ -115,7 +115,7 @@ class RepeatedLiteralFixes:
115
115
 
116
116
  def _stable_binding(self, binding: ast.Name, literal: ast.Constant, parent: ast.FunctionDef) -> bool:
117
117
  return (
118
- self.provenance.bindings.get(parent, {}).get(binding.id) == [binding]
118
+ self.provenance.bindings.get(parent, {}).get(binding.id) == (binding,)
119
119
  and self.provenance.binding(binding.id, literal) is binding
120
120
  and binding.id not in self.provenance.external_bindings
121
121
  and (binding.lineno, binding.col_offset + 1) not in self.locations
@@ -8,10 +8,12 @@ from sarj_python_lint._analysis_session import AnalysisSession
8
8
  from sarj_python_lint._source import python_source_lines, symbol_prefilter_source
9
9
  from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics
10
10
  from sarj_python_lint.rules._ast_index import NodeIndex
11
+ from sarj_python_lint.rules._binding_inventory import BindingInventory
11
12
  from sarj_python_lint.rules._fastapi import FastapiIndex
12
13
  from sarj_python_lint.rules._imports import ImportIndex
13
14
  from sarj_python_lint.rules._namespace_access import dynamic_namespace_access
14
15
  from sarj_python_lint.rules._paths import is_generated
16
+ from sarj_python_lint.rules._runtime_bindings import RuntimeBindings
15
17
 
16
18
 
17
19
  if TYPE_CHECKING:
@@ -23,7 +25,7 @@ if TYPE_CHECKING:
23
25
  from sarj_python_lint.rules._test_provenance import TestProvenance
24
26
 
25
27
 
26
- @final
28
+ @final # ruff: ignore[too-many-public-methods] -- one file owns the lazy fact caches and their shared lifetime; splitting providers would duplicate semantic state.
27
29
  class PythonFileContext:
28
30
  def __init__(self, path: Path, source: str, session: AnalysisSession | None = None) -> None:
29
31
  self.path = path
@@ -85,6 +87,14 @@ class PythonFileContext:
85
87
  def parents(self) -> Mapping[ast.AST, ast.AST]:
86
88
  return self.node_index.parents
87
89
 
90
+ @cached_property
91
+ def binding_inventory(self) -> BindingInventory:
92
+ return BindingInventory.from_context(self)
93
+
94
+ @cached_property
95
+ def runtime_bindings(self) -> RuntimeBindings:
96
+ return RuntimeBindings(self, self.binding_inventory.bindings)
97
+
88
98
  @cached_property
89
99
  def imports(self) -> ImportIndex:
90
100
  return ImportIndex.from_tree(self._valid_tree())
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import ast
4
+ from dataclasses import dataclass
4
5
  from typing import TYPE_CHECKING
5
6
 
6
7
  from sarj_python_lint.rules._imports import ImportIndex
@@ -10,6 +11,15 @@ if TYPE_CHECKING:
10
11
  from sarj_python_lint._file_context import PythonFileContext
11
12
 
12
13
 
14
+ _MAX_ALIAS_DEPTH = 32
15
+
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class ResolvedAnnotation:
19
+ expression: ast.expr
20
+ at: ast.expr
21
+
22
+
13
23
  class AnnotationOrigins:
14
24
  def __init__(self, context: PythonFileContext) -> None:
15
25
  self.context = context
@@ -19,6 +29,39 @@ class AnnotationOrigins:
19
29
  for node in context.nodes(ast.Import, ast.ImportFrom)
20
30
  }
21
31
 
32
+ def alias_value(self, node: ast.Name, at: ast.AST) -> ast.expr | None:
33
+ value = self.context.annotation_semantics.aliases.get(node.id)
34
+ if value is None:
35
+ return None
36
+ binding = self.provenance.binding(node.id, at)
37
+ if not isinstance(binding, ast.Name):
38
+ return None
39
+ parent = self.context.parents.get(binding)
40
+ if isinstance(parent, ast.TypeAlias | ast.AnnAssign) and parent.value is value:
41
+ return value
42
+ return None
43
+
44
+ def resolve_annotation(self, node: ast.expr, *, at: ast.expr | None = None) -> ResolvedAnnotation | None:
45
+ current = node
46
+ scope = node if at is None else at
47
+ seen: set[str] = set()
48
+ for _ in range(_MAX_ALIAS_DEPTH):
49
+ parsed = self.context.annotation_semantics.parse(current)
50
+ if parsed is None:
51
+ return None
52
+ if isinstance(parsed, ast.Name):
53
+ if self.shadowed(parsed.id, scope):
54
+ return None
55
+ value = self.alias_value(parsed, scope)
56
+ if value is not None:
57
+ if parsed.id in seen:
58
+ return None
59
+ seen.add(parsed.id)
60
+ current = scope = value
61
+ continue
62
+ return ResolvedAnnotation(parsed, scope)
63
+ return None
64
+
22
65
  def shadowed(self, name: str, at: ast.AST) -> bool:
23
66
  return type_parameter_shadow(name, at, self.context)
24
67
 
@@ -158,10 +158,13 @@ def _bound_names(statement: ast.stmt) -> set[str]:
158
158
 
159
159
 
160
160
  def _target_names(node: ast.expr) -> set[str]:
161
- if isinstance(node, ast.Name):
162
- return {node.id}
163
- if isinstance(node, (ast.List, ast.Tuple)):
164
- return {name for item in node.elts for name in _target_names(item)}
161
+ match node:
162
+ case ast.Name():
163
+ return {node.id}
164
+ case ast.List() | ast.Tuple():
165
+ return {name for item in node.elts for name in _target_names(item)}
166
+ case _:
167
+ pass
165
168
  return set()
166
169
 
167
170
 
@@ -0,0 +1,131 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from collections import defaultdict
5
+ from collections.abc import Mapping
6
+ from dataclasses import dataclass
7
+ from types import MappingProxyType
8
+ from typing import TYPE_CHECKING, final
9
+
10
+
11
+ if TYPE_CHECKING:
12
+ from sarj_python_lint._file_context import PythonFileContext
13
+
14
+
15
+ _SCOPES = (
16
+ ast.Module,
17
+ ast.FunctionDef,
18
+ ast.AsyncFunctionDef,
19
+ ast.ClassDef,
20
+ ast.Lambda,
21
+ ast.ListComp,
22
+ ast.SetComp,
23
+ ast.DictComp,
24
+ ast.GeneratorExp,
25
+ )
26
+
27
+
28
+ @final
29
+ @dataclass(frozen=True, slots=True)
30
+ class BindingInventory:
31
+ parents: Mapping[ast.AST, ast.AST]
32
+ bindings: Mapping[ast.AST | None, Mapping[str, tuple[ast.AST, ...]]]
33
+ reads: Mapping[str, tuple[ast.Name, ...]]
34
+ wildcard: bool
35
+ mutated: frozenset[str]
36
+ external_bindings: frozenset[str]
37
+
38
+ @classmethod
39
+ def from_context(cls, context: PythonFileContext) -> BindingInventory:
40
+ parents = context.parents
41
+ bindings: dict[ast.AST | None, dict[str, list[ast.AST]]] = defaultdict(lambda: defaultdict(list))
42
+ reads: dict[str, list[ast.Name]] = defaultdict(list)
43
+ mutated: set[str] = set()
44
+ external_bindings: set[str] = set()
45
+ wildcard = False
46
+ for node in context.nodes(ast.AST):
47
+ if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Load):
48
+ reads[node.id].append(node)
49
+ for name in _bound_names(node):
50
+ bindings[_binding_scope(parents, node)][name].append(node)
51
+ wildcard |= name == "*"
52
+ if isinstance(node, ast.Global | ast.Nonlocal):
53
+ external_bindings.update(node.names)
54
+ if (name := _mutated_namespace(node)) is not None:
55
+ mutated.add(name)
56
+ return cls(
57
+ parents=parents,
58
+ bindings=MappingProxyType(
59
+ {
60
+ scope: MappingProxyType({name: tuple(nodes) for name, nodes in names.items()})
61
+ for scope, names in bindings.items()
62
+ }
63
+ ),
64
+ reads=MappingProxyType({name: tuple(nodes) for name, nodes in reads.items()}),
65
+ wildcard=wildcard,
66
+ mutated=frozenset(mutated),
67
+ external_bindings=frozenset(external_bindings),
68
+ )
69
+
70
+ def scope(self, node: ast.AST) -> ast.AST | None:
71
+ return _scope(self.parents, node)
72
+
73
+
74
+ def _mutated_namespace(node: ast.AST) -> str | None:
75
+ if not isinstance(node, ast.Attribute) or not isinstance(node.ctx, ast.Store | ast.Del):
76
+ return None
77
+ root = node.value
78
+ while isinstance(root, ast.Attribute):
79
+ root = root.value
80
+ return root.id if isinstance(root, ast.Name) else None
81
+
82
+
83
+ def _scope(parents: Mapping[ast.AST, ast.AST], node: ast.AST) -> ast.AST | None:
84
+ parent = parents.get(node)
85
+ while parent is not None:
86
+ if isinstance(parent, ast.arguments) and (node in parent.defaults or node in parent.kw_defaults):
87
+ parent = parents.get(parent)
88
+ elif isinstance(parent, _SCOPES) and not (
89
+ (
90
+ isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef)
91
+ and node in parent.decorator_list
92
+ )
93
+ or (isinstance(parent, ast.ClassDef) and (node in parent.bases or node in parent.keywords))
94
+ ):
95
+ return parent
96
+ if parent is None:
97
+ return None
98
+ node = parent
99
+ parent = parents.get(parent)
100
+ return None
101
+
102
+
103
+ def _binding_scope(parents: Mapping[ast.AST, ast.AST], node: ast.AST) -> ast.AST | None:
104
+ scope = _scope(parents, node)
105
+ parent = parents.get(node)
106
+ if isinstance(parent, ast.NamedExpr) and parent.target is node:
107
+ while isinstance(scope, ast.ListComp | ast.SetComp | ast.DictComp | ast.GeneratorExp):
108
+ scope = _scope(parents, scope)
109
+ return scope
110
+
111
+
112
+ def _bound_names(node: ast.AST) -> tuple[str, ...]:
113
+ match node:
114
+ case ast.Name(id=name, ctx=ast.Store() | ast.Del()) | ast.arg(arg=name):
115
+ return (name,)
116
+ case ast.alias():
117
+ return (node.asname or node.name.partition(".")[0],)
118
+ case (
119
+ ast.FunctionDef(name=name)
120
+ | ast.AsyncFunctionDef(name=name)
121
+ | ast.ClassDef(name=name)
122
+ | ast.ExceptHandler(name=str() as name)
123
+ | ast.MatchAs(name=str() as name)
124
+ | ast.MatchStar(name=str() as name)
125
+ | ast.MatchMapping(rest=str() as name)
126
+ ):
127
+ return (name,)
128
+ case ast.Global(names=names) | ast.Nonlocal(names=names):
129
+ return tuple(names)
130
+ case _:
131
+ return ()
@@ -115,7 +115,7 @@ def call_matches(function: ast.FunctionDef, call: ast.Call) -> bool:
115
115
  def stable_class(context: PythonFileContext, declaration: ast.ClassDef, provenance: ResourceProvenance) -> bool:
116
116
  if declaration.decorator_list or declaration.keywords or context.parents.get(declaration) is not context.tree:
117
117
  return False
118
- return provenance.bindings.get(context.tree, {}).get(declaration.name) == [declaration]
118
+ return provenance.bindings.get(context.tree, {}).get(declaration.name) == (declaration,)
119
119
 
120
120
 
121
121
  def plain_class_members(
@@ -659,28 +659,37 @@ class CopyOnWriteAnalysis:
659
659
  return _Value(_Shape("dict", element=element), shared_children=shared, fields=tuple(fields.items()))
660
660
 
661
661
  def _write(self, target: ast.expr, bindings: Mapping[str, _Value]) -> None:
662
- if isinstance(target, (ast.Tuple, ast.List)):
663
- for item in target.elts:
664
- self._write(item, bindings)
665
- elif isinstance(target, (ast.Attribute, ast.Subscript)):
666
- owner = self._value(target.value, bindings)
667
- if (
668
- owner is not None
669
- and owner.borrowed
670
- and (
671
- isinstance(target, ast.Subscript)
672
- or (owner.shape.model is not None and self.models.field(owner.shape.model, target.attr) is not None)
673
- )
674
- ):
675
- self.mutations.append(Mutation(target, self._before))
662
+ match target:
663
+ case ast.Tuple() | ast.List():
664
+ for item in target.elts:
665
+ self._write(item, bindings)
666
+ case ast.Attribute() | ast.Subscript():
667
+ owner = self._value(target.value, bindings)
668
+ if (
669
+ owner is not None
670
+ and owner.borrowed
671
+ and (
672
+ isinstance(target, ast.Subscript)
673
+ or (
674
+ owner.shape.model is not None
675
+ and self.models.field(owner.shape.model, target.attr) is not None
676
+ )
677
+ )
678
+ ):
679
+ self.mutations.append(Mutation(target, self._before))
680
+ case _:
681
+ pass
676
682
 
677
683
  def _expression(self, node: ast.AST, bindings: dict[str, _Value]) -> None:
678
- if isinstance(node, (ast.Lambda, ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
679
- return
680
- if isinstance(node, ast.NamedExpr):
681
- self._expression(node.value, bindings)
682
- self._bind(node.target, self._value(node.value, bindings), bindings)
683
- return
684
+ match node:
685
+ case ast.Lambda() | ast.FunctionDef() | ast.AsyncFunctionDef() | ast.ClassDef():
686
+ return
687
+ case ast.NamedExpr():
688
+ self._expression(node.value, bindings)
689
+ self._bind(node.target, self._value(node.value, bindings), bindings)
690
+ return
691
+ case _:
692
+ pass
684
693
  if isinstance(node, ast.Call) and self._mutator_call(node, bindings):
685
694
  self.mutations.append(Mutation(node, self._before))
686
695
  if isinstance(node, (ast.ListComp, ast.SetComp, ast.GeneratorExp, ast.DictComp)):
@@ -11,6 +11,8 @@ from sarj_python_lint.rules._resource_provenance import ResourceProvenance
11
11
 
12
12
 
13
13
  if TYPE_CHECKING:
14
+ from collections.abc import Mapping, Sequence
15
+
14
16
  from sarj_python_lint._file_context import PythonFileContext
15
17
 
16
18
 
@@ -757,7 +759,7 @@ def _metadata_tuple_index(node: ast.expr) -> int | None:
757
759
  return None
758
760
 
759
761
 
760
- def _metadata_alias_graph(tree: ast.Module, bindings: dict[str, list[ast.AST]]) -> dict[str, set[str]]:
762
+ def _metadata_alias_graph(tree: ast.Module, bindings: Mapping[str, Sequence[ast.AST]]) -> dict[str, set[str]]:
761
763
  graph: dict[str, set[str]] = {}
762
764
  for node in tree.body:
763
765
  binding = _value_binding(node)
@@ -305,10 +305,13 @@ def base_names(node: ast.ClassDef) -> set[str]:
305
305
  names: set[str] = set()
306
306
  for base in node.bases:
307
307
  target = base.value if isinstance(base, ast.Subscript) else base
308
- if isinstance(target, ast.Attribute):
309
- names.add(target.attr)
310
- elif isinstance(target, ast.Name):
311
- names.add(target.id)
308
+ match target:
309
+ case ast.Attribute():
310
+ names.add(target.attr)
311
+ case ast.Name():
312
+ names.add(target.id)
313
+ case _:
314
+ pass
312
315
  return names
313
316
 
314
317
 
@@ -241,10 +241,13 @@ class FastapiIndex:
241
241
  for node in walk_ast(tree):
242
242
  if self._node_scopes[id(node)] != 0:
243
243
  continue
244
- if isinstance(node, ast.Import):
245
- self._read_import(node)
246
- elif isinstance(node, ast.ImportFrom):
247
- self._read_import_from(node)
244
+ match node:
245
+ case ast.Import():
246
+ self._read_import(node)
247
+ case ast.ImportFrom():
248
+ self._read_import_from(node)
249
+ case _:
250
+ pass
248
251
 
249
252
  def _read_import(self, node: ast.Import) -> None:
250
253
  for alias in node.names:
@@ -141,10 +141,13 @@ def _module_non_import_bindings(tree: ast.Module) -> set[str]:
141
141
 
142
142
 
143
143
  def _target_names(node: ast.expr) -> set[str]:
144
- if isinstance(node, ast.Name):
145
- return {node.id}
146
- if isinstance(node, (ast.Tuple, ast.List)):
147
- return {name for element in node.elts for name in _target_names(element)}
144
+ match node:
145
+ case ast.Name():
146
+ return {node.id}
147
+ case ast.Tuple() | ast.List():
148
+ return {name for element in node.elts for name in _target_names(element)}
149
+ case _:
150
+ pass
148
151
  return set()
149
152
 
150
153
 
@@ -59,14 +59,17 @@ class RawMappingCallbacks:
59
59
  def callback(self, node: ast.expr, *, seen: frozenset[ast.AST] = frozenset()) -> _Function | None:
60
60
  if len(seen) >= _MAX_DEPTH:
61
61
  return None
62
- if isinstance(node, ast.Call):
63
- return (
64
- self._partial(node, seen)
65
- if self.qualified(node.func, node) == "functools.partial"
66
- else self._method(node, "__call__", seen)
67
- )
68
- if isinstance(node, ast.Attribute):
69
- return self._method(node.value, node.attr, seen)
62
+ match node:
63
+ case ast.Call():
64
+ return (
65
+ self._partial(node, seen)
66
+ if self.qualified(node.func, node) == "functools.partial"
67
+ else self._method(node, "__call__", seen)
68
+ )
69
+ case ast.Attribute():
70
+ return self._method(node.value, node.attr, seen)
71
+ case _:
72
+ pass
70
73
  if not isinstance(node, ast.Name) or not self._stable(node.id) or self.shadowed(node.id, node):
71
74
  return None
72
75
  binding = self.provenance.binding(node.id, node)
@@ -144,10 +147,13 @@ class RawMappingCallbacks:
144
147
  def _function(self, node: ast.AST) -> _Function | None:
145
148
  current = self.context.parents.get(node)
146
149
  while current is not None:
147
- if isinstance(current, ast.FunctionDef | ast.AsyncFunctionDef):
148
- return current
149
- if isinstance(current, ast.ClassDef | ast.Lambda):
150
- return None
150
+ match current:
151
+ case ast.FunctionDef() | ast.AsyncFunctionDef():
152
+ return current
153
+ case ast.ClassDef() | ast.Lambda():
154
+ return None
155
+ case _:
156
+ pass
151
157
  current = self.context.parents.get(current)
152
158
  return None
153
159
 
@@ -406,11 +406,14 @@ class _NamespaceFlow:
406
406
  def plain_dict(self, value: ast.expr, *, depth: int = 0) -> bool:
407
407
  if depth >= _MAX_DEPTH:
408
408
  return False
409
- if isinstance(value, ast.Name):
410
- assigned = self.assigned(value)
411
- return assigned is not None and self.plain_dict(assigned, depth=depth + 1)
412
- if isinstance(value, ast.IfExp):
413
- return self.plain_dict(value.body, depth=depth + 1) and self.plain_dict(value.orelse, depth=depth + 1)
409
+ match value:
410
+ case ast.Name():
411
+ assigned = self.assigned(value)
412
+ return assigned is not None and self.plain_dict(assigned, depth=depth + 1)
413
+ case ast.IfExp():
414
+ return self.plain_dict(value.body, depth=depth + 1) and self.plain_dict(value.orelse, depth=depth + 1)
415
+ case _:
416
+ pass
414
417
  return isinstance(value, ast.Dict) or (isinstance(value, ast.Call) and self.native.dict_call(value))
415
418
 
416
419
  def _collect(self, value: ast.expr, *, execution: ast.Call, depth: int = 0) -> None:
@@ -436,10 +439,13 @@ class _NamespaceFlow:
436
439
  def _input(self, value: ast.expr, *, depth: int = 0) -> set[ast.arg]:
437
440
  if depth >= _MAX_DEPTH:
438
441
  return set()
439
- if isinstance(value, ast.Name):
440
- return self._name_input(value, depth)
441
- if isinstance(value, ast.Dict):
442
- return self._dictionary_inputs(value, depth)
442
+ match value:
443
+ case ast.Name():
444
+ return self._name_input(value, depth)
445
+ case ast.Dict():
446
+ return self._dictionary_inputs(value, depth)
447
+ case _:
448
+ pass
443
449
  if isinstance(value, ast.Call) and self.native.dict_call(value):
444
450
  return self._constructor_inputs(value, depth)
445
451
  found: set[ast.arg] = set()
@@ -652,13 +658,16 @@ class _NamespaceEscapes:
652
658
  return None
653
659
  binding = self.flow.binding(value)
654
660
  found: set[ast.AST] = {binding} if binding is not None else set()
655
- if isinstance(value, ast.Name):
656
- assigned = self.flow.assigned(value)
657
- if isinstance(assigned, ast.Name | ast.Subscript):
658
- previous = self._carrier_roots(assigned, depth=depth + 1)
659
- return None if previous is None else found | previous
660
- elif isinstance(value, ast.Subscript):
661
- return self._carrier_roots(value.value, depth=depth + 1)
661
+ match value:
662
+ case ast.Name():
663
+ assigned = self.flow.assigned(value)
664
+ if isinstance(assigned, ast.Name | ast.Subscript):
665
+ previous = self._carrier_roots(assigned, depth=depth + 1)
666
+ return None if previous is None else found | previous
667
+ case ast.Subscript():
668
+ return self._carrier_roots(value.value, depth=depth + 1)
669
+ case _:
670
+ pass
662
671
  return found
663
672
 
664
673
 
@@ -427,10 +427,13 @@ class OpaqueContractFacts:
427
427
 
428
428
  def _enclosing_assertion(self, node: ast.AST) -> ast.Assert | None:
429
429
  while (parent := self.context.parents.get(node)) is not None:
430
- if isinstance(parent, ast.Assert):
431
- return parent
432
- if isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef | ast.Lambda):
433
- return None
430
+ match parent:
431
+ case ast.Assert():
432
+ return parent
433
+ case ast.FunctionDef() | ast.AsyncFunctionDef() | ast.Lambda():
434
+ return None
435
+ case _:
436
+ pass
434
437
  node = parent
435
438
  return None
436
439
 
@@ -534,11 +537,14 @@ class OpaqueContractFacts:
534
537
  return None
535
538
  if isinstance(node, ast.Name):
536
539
  binding = self.facts.provenance.runtime_binding(node.id, node)
537
- if isinstance(binding, ast.arg):
538
- return binding
539
- if isinstance(binding, ast.Name):
540
- value = self.assigned(binding, node)
541
- return self.parameter(value, depth=depth + 1) if value is not None else None
540
+ match binding:
541
+ case ast.arg():
542
+ return binding
543
+ case ast.Name():
544
+ value = self.assigned(binding, node)
545
+ return self.parameter(value, depth=depth + 1) if value is not None else None
546
+ case _:
547
+ pass
542
548
  receiver = _projection_receiver(node)
543
549
  parameter = self.parameter(receiver, depth=depth + 1) if receiver is not None else None
544
550
  return parameter if self._raw_annotation(parameter) else None
@@ -993,12 +999,13 @@ def _projection_receiver(node: ast.expr) -> ast.expr | None:
993
999
 
994
1000
 
995
1001
  def _control_condition(node: ast.AST, parent: ast.AST) -> tuple[ast.expr, bool] | None:
996
- if isinstance(parent, ast.IfExp):
997
- accepted, rejected = node is parent.body, node is parent.orelse
998
- elif isinstance(parent, ast.If):
999
- accepted, rejected = node in parent.body, node in parent.orelse
1000
- else:
1001
- return None
1002
+ match parent:
1003
+ case ast.IfExp():
1004
+ accepted, rejected = node is parent.body, node is parent.orelse
1005
+ case ast.If():
1006
+ accepted, rejected = node in parent.body, node in parent.orelse
1007
+ case _:
1008
+ return None
1002
1009
  return (parent.test, accepted) if accepted or rejected else None
1003
1010
 
1004
1011