sarj-python-lint 1.28.1__tar.gz → 1.28.3__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 (192) hide show
  1. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/PKG-INFO +2 -2
  2. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/pyproject.toml +2 -2
  3. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/_file_context.py +4 -0
  4. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_resource_provenance.py +10 -0
  5. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +23 -3
  6. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +4 -3
  7. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +8 -6
  8. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +1 -8
  9. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +1 -10
  10. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +1 -9
  11. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +1 -9
  12. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +4 -1
  13. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +2 -0
  14. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/unused_mock_setup.py +159 -78
  15. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/.gitignore +0 -0
  16. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/LICENSE +0 -0
  17. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/README.md +0 -0
  18. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/__init__.py +0 -0
  19. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/__main__.py +0 -0
  20. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/_analysis_session.py +0 -0
  21. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/_filesystem.py +0 -0
  22. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/_python_target.py +0 -0
  23. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  24. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/_secret_names.py +0 -0
  25. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/_source.py +0 -0
  26. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/_version.py +0 -0
  27. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/benchmark.py +0 -0
  28. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/interpreter_argv.py +0 -0
  29. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/json_boundary.py +0 -0
  30. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/py.typed +0 -0
  31. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/ratchet.py +0 -0
  32. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rule_base.py +0 -0
  33. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/__init__.py +0 -0
  34. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  35. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  36. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  37. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  38. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_comments.py +0 -0
  39. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  40. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  41. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  42. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  43. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  44. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_first_party.py +0 -0
  45. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  46. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_imports.py +0 -0
  47. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_local_source.py +0 -0
  48. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_logging.py +0 -0
  49. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  50. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_paths.py +0 -0
  51. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_project_index.py +0 -0
  52. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  53. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_pytest.py +0 -0
  54. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_registry.py +0 -0
  55. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  56. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_sql.py +0 -0
  57. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  58. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  59. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  60. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  61. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  62. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  63. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  64. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  65. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  66. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  67. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  68. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  69. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  70. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  71. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  72. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  73. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  74. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  75. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  76. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  77. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  78. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  79. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  80. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  81. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  82. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  83. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  84. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  85. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  86. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  87. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  88. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  89. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  90. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  91. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  92. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  93. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  94. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  95. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  96. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  97. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  98. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  99. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  100. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  101. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  102. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  103. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  104. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  105. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  106. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  107. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  108. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  109. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  110. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  111. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  112. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  113. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  114. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  115. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  116. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  117. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  118. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  119. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  120. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  121. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  122. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  123. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  124. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  125. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  126. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  127. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  128. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  129. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  130. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  131. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  132. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  133. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  134. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  135. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  136. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  137. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  138. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  139. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  140. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  141. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  142. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  143. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  144. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  145. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  146. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  147. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  148. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  149. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  150. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  151. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  152. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  153. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  154. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  155. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  156. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  157. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  158. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  159. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  160. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  161. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  162. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  163. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  164. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  165. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  166. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  167. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  168. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  169. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  170. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  171. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  172. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  173. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  174. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  175. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  176. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  177. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  178. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  179. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  180. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  181. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  182. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  183. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  184. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/stepdown.py +0 -0
  185. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  186. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  187. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  188. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  189. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  190. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  191. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  192. {sarj_python_lint-1.28.1 → sarj_python_lint-1.28.3}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 1.28.1
3
+ Version: 1.28.3
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/
@@ -19,7 +19,7 @@ Classifier: Topic :: Software Development :: Quality Assurance
19
19
  Requires-Python: >=3.14
20
20
  Requires-Dist: packaging==26.3
21
21
  Requires-Dist: ruff>=0.16.10
22
- Requires-Dist: sarj-rule-contracts==0.2.2
22
+ Requires-Dist: sarj-rule-contracts==0.2.3
23
23
  Requires-Dist: sqlglot==30.19.0
24
24
  Requires-Dist: typer==0.27.2
25
25
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sarj-python-lint"
3
- version = "1.28.1"
3
+ version = "1.28.3"
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" }]
@@ -15,7 +15,7 @@ classifiers = [
15
15
  "Programming Language :: Python :: 3.14",
16
16
  "Topic :: Software Development :: Quality Assurance",
17
17
  ]
18
- dependencies = ["sarj-rule-contracts==0.2.2", "packaging==26.3", "ruff>=0.16.10", "sqlglot==30.19.0", "typer==0.27.2"]
18
+ dependencies = ["sarj-rule-contracts==0.2.3", "packaging==26.3", "ruff>=0.16.10", "sqlglot==30.19.0", "typer==0.27.2"]
19
19
 
20
20
  [project.scripts]
21
21
  sarj-python-lint = "sarj_python_lint.__main__:main"
@@ -57,6 +57,10 @@ class PythonFileContext:
57
57
  def nodes[NodeT: ast.AST](self, *types: type[NodeT]) -> list[NodeT]:
58
58
  return [node for node in self.node_index.query(types) if isinstance(node, types)]
59
59
 
60
+ @cached_property
61
+ def has_wildcard_import(self) -> bool:
62
+ return any(alias.name == "*" for node in self.nodes(ast.ImportFrom) for alias in node.names)
63
+
60
64
  @property
61
65
  def parents(self) -> Mapping[ast.AST, ast.AST]:
62
66
  return self.node_index.parents
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import ast
4
4
  from collections import defaultdict
5
+ from functools import cached_property
5
6
  from typing import TYPE_CHECKING, final
6
7
 
7
8
 
@@ -44,6 +45,15 @@ class ResourceProvenance:
44
45
  if root is not None:
45
46
  self.mutated.add(root)
46
47
 
48
+ @cached_property
49
+ def builtin_namespace_untouched(self) -> bool:
50
+ return not any(
51
+ (isinstance(node, ast.Import) and any(alias.name == "builtins" for alias in node.names))
52
+ or (isinstance(node, ast.ImportFrom) and node.module == "builtins")
53
+ or (isinstance(node, ast.Name) and node.id == "__builtins__")
54
+ for node in self.context.nodes(ast.Import, ast.ImportFrom, ast.Name)
55
+ )
56
+
47
57
  def scope(self, node: ast.AST) -> ast.AST | None:
48
58
  parent = self.context.parents.get(node)
49
59
  while parent is not None and not isinstance(parent, _SCOPES):
@@ -59,8 +59,8 @@ class DuplicateAdjacentExceptionHandler(ProjectRule):
59
59
  autofix=AutofixPolicy.NONE,
60
60
  limitations=(
61
61
  "Only ordinary try statements with adjacent handlers, identical exception bindings, executable ASTs and comments are compared; exception groups and bare re-raise/pass handlers are excluded.",
62
- "Handler types must be simple names or tuples of unshadowed builtin or statically resolved first-party Exception subclasses, with unconditional stable declarations/imports.",
63
- "Dynamic namespace access, unknown external exception classes, generated/vendor sources, different comments, and intervening handlers are excluded. No autofix preserves intentional translation seams.",
62
+ "Handler types must be simple names or tuples of unshadowed builtin or statically resolved first-party Exception subclasses, with stable declarations/imports initialized before the enclosing module statement. Declarations after a callable's definition are unresolved and excluded.",
63
+ "Dynamic or explicit builtin namespace access, unknown external exception classes, generated/vendor sources, different comments, and intervening handlers are excluded. Unseen external builtin mutations are not resolved. No autofix preserves intentional translation seams.",
64
64
  ),
65
65
  examples=tuple(
66
66
  RuleExample(
@@ -88,6 +88,8 @@ class DuplicateAdjacentExceptionHandler(ProjectRule):
88
88
  project = context.session.project or ProjectIndexSet.single(context.path, context.source)
89
89
  unit = project.unit_or_source(context.path, context.source, tree)
90
90
  provenance = ResourceProvenance(context)
91
+ if not provenance.builtin_namespace_untouched:
92
+ return []
91
93
  comments = scan_comments(context.source)
92
94
  findings: list[Diagnostic] = []
93
95
  for statement in context.nodes(ast.Try):
@@ -176,7 +178,11 @@ def _first_party_exception(
176
178
  from sarj_python_lint._file_context import PythonFileContext # ruff: ignore[import-outside-top-level] -- defer until the rule registry finishes loading.
177
179
 
178
180
  binding = provenance.binding(expression.id, expression)
179
- if not isinstance(binding, ast.ClassDef | ast.alias) or unit is None:
181
+ if (
182
+ not isinstance(binding, ast.ClassDef | ast.alias)
183
+ or unit is None
184
+ or not _initialized_before(binding, expression, context)
185
+ ):
180
186
  return False
181
187
  if isinstance(binding, ast.alias):
182
188
  statement = context.parents.get(binding)
@@ -200,6 +206,7 @@ def _first_party_exception(
200
206
  )
201
207
  return (
202
208
  declaration is not None
209
+ and definition_provenance.builtin_namespace_untouched
203
210
  and stable_class(definition_context, declaration, definition_provenance)
204
211
  and len(declaration.bases) == 1
205
212
  and _safe_types(
@@ -211,3 +218,16 @@ def _first_party_exception(
211
218
  seen=seen | {summary.symbol},
212
219
  )
213
220
  )
221
+
222
+
223
+ def _initialized_before(binding: ast.ClassDef | ast.alias, read: ast.Name, context: PythonFileContext) -> bool:
224
+ declaration = context.parents.get(binding) if isinstance(binding, ast.alias) else binding
225
+ boundary: ast.AST = read
226
+ while (parent := context.parents.get(boundary)) is not None and parent is not context.tree:
227
+ boundary = parent
228
+ return (
229
+ isinstance(declaration, ast.stmt)
230
+ and context.parents.get(declaration) is context.tree
231
+ and isinstance(boundary, ast.stmt)
232
+ and (declaration.end_lineno or declaration.lineno) < boundary.lineno
233
+ )
@@ -131,11 +131,12 @@ def _pytest_plugin_test_targets(tree: ast.Module) -> list[_PluginTarget]:
131
131
  for statement in tree.body:
132
132
  value: ast.expr | None = None
133
133
  match statement:
134
- case (
135
- ast.Assign(targets=[ast.Name(id="pytest_plugins")])
136
- | ast.AnnAssign(target=ast.Name(id="pytest_plugins"))
134
+ case ast.Assign(targets=targets) if any(
135
+ isinstance(target, ast.Name) and target.id == "pytest_plugins" for target in targets
137
136
  ):
138
137
  value = statement.value
138
+ case ast.AnnAssign(target=ast.Name(id="pytest_plugins")):
139
+ value = statement.value
139
140
  case _:
140
141
  continue
141
142
  findings.extend(
@@ -57,10 +57,10 @@ class OpaqueParametrizeCaseNeedsId(Rule):
57
57
  code: str = "SARJ042"
58
58
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
59
59
  default_level=Severity.WARNING,
60
- summary="Opaque static pytest parameter cases rely on argument-name-and-index fallback IDs.",
61
- rationale="Fallback IDs such as `payload0` are hard to diagnose and silently change when the table is reordered.",
60
+ summary="Opaque static pytest parameter cases lack table-local semantic IDs.",
61
+ rationale="Without a semantic naming owner, fallback IDs such as `payload0` are hard to diagnose and silently change when the table is reordered.",
62
62
  remediation=(
63
- "Give each opaque row a semantic `pytest.param(..., id=...)`, or use a semantic callable `ids=` when one "
63
+ "Check the owning pytest_make_parametrize_id hook before adding IDs; reuse an existing semantic naming owner. Otherwise give each opaque row a semantic `pytest.param(..., id=...)`, or use a semantic callable `ids=` when one "
64
64
  "mapping applies to the entire table."
65
65
  ),
66
66
  category=RuleCategory.TESTING,
@@ -129,10 +129,12 @@ class OpaqueParametrizeCaseNeedsId(Rule):
129
129
 
130
130
 
131
131
  def _message(count: int) -> str:
132
- subject = "case relies" if count == 1 else "cases rely"
132
+ subject = "case lacks" if count == 1 else "cases lack"
133
133
  return (
134
- f"{count} {subject} on pytest's argument-name-and-index fallback (for example, `payload0`), which renumbers "
135
- 'when rows are reordered. Add semantic `pytest.param(..., id="...")` values or a semantic callable `ids=`.'
134
+ f"{count} {subject} a table-local semantic ID. Check the owning `pytest_make_parametrize_id` hook before "
135
+ 'adding IDs; reuse an existing semantic naming owner, or add semantic `pytest.param(..., id="...")` values '
136
+ "or a semantic callable `ids=`. Without a semantic ID, pytest may use an argument-name-and-index fallback "
137
+ "(for example, `payload0`), which renumbers when rows are reordered."
136
138
  )
137
139
 
138
140
 
@@ -177,7 +177,7 @@ class PreferMatchExceptionDispatch(Rule):
177
177
  imports = context.imports
178
178
  if (
179
179
  not imports.builtin_is_unshadowed("isinstance")
180
- or _has_wildcard_import(tree, node_index=context.node_index)
180
+ or context.has_wildcard_import
181
181
  or _has_local_import(tree, node_index=context.node_index)
182
182
  ):
183
183
  return []
@@ -412,13 +412,6 @@ def _body_terminates(body: list[ast.stmt]) -> bool:
412
412
  return False
413
413
 
414
414
 
415
- def _has_wildcard_import(tree: ast.Module, *, node_index: NodeIndex | None = None) -> bool:
416
- return any(
417
- isinstance(node, ast.ImportFrom) and any(alias.name == "*" for alias in node.names)
418
- for node in walk_ast(tree, index=node_index)
419
- )
420
-
421
-
422
415
  def _has_local_import(tree: ast.Module, *, node_index: NodeIndex | None = None) -> bool:
423
416
  module_imports = {id(statement) for statement in tree.body if isinstance(statement, (ast.Import, ast.ImportFrom))}
424
417
  return any(
@@ -16,12 +16,10 @@ from sarj_python_lint.rule_base import (
16
16
  Severity,
17
17
  parse_or_none,
18
18
  )
19
- from sarj_python_lint.rules._ast_index import walk as walk_ast
20
19
 
21
20
 
22
21
  if TYPE_CHECKING:
23
22
  from sarj_python_lint._file_context import PythonFileContext
24
- from sarj_python_lint.rules._ast_index import NodeIndex
25
23
  from sarj_python_lint.rules._imports import ImportIndex
26
24
 
27
25
 
@@ -101,7 +99,7 @@ class PreferNamedtupleOverTupleReturn(Rule):
101
99
  if context.generated or _is_documentation_path(path):
102
100
  return []
103
101
  tree = context.tree
104
- if tree is None or _has_wildcard_import(tree, node_index=context.node_index):
102
+ if tree is None or context.has_wildcard_import:
105
103
  return []
106
104
  imports = context.imports
107
105
  diagnostics: list[Diagnostic] = []
@@ -127,13 +125,6 @@ def _is_documentation_path(path: Path) -> bool:
127
125
  return any(part.lower() in _DOCUMENTATION_DIR_NAMES for part in path.parts)
128
126
 
129
127
 
130
- def _has_wildcard_import(tree: ast.Module, *, node_index: NodeIndex | None = None) -> bool:
131
- return any(
132
- isinstance(node, ast.ImportFrom) and any(alias.name == "*" for alias in node.names)
133
- for node in walk_ast(tree, index=node_index)
134
- )
135
-
136
-
137
128
  def _is_public_record_tuple(annotation: ast.expr, imports: ImportIndex) -> bool:
138
129
  annotation = _unwrap_return_annotation(annotation, imports)
139
130
  if not isinstance(annotation, ast.Subscript) or not isinstance(annotation.slice, ast.Tuple):
@@ -27,7 +27,6 @@ if TYPE_CHECKING:
27
27
  from pathlib import Path
28
28
 
29
29
  from sarj_python_lint._file_context import PythonFileContext
30
- from sarj_python_lint.rules._ast_index import NodeIndex
31
30
  from sarj_python_lint.rules._imports import ImportIndex
32
31
 
33
32
 
@@ -221,7 +220,7 @@ class PreferNominalIdTypes(Rule):
221
220
  if _is_excluded_path(path) or context.generated:
222
221
  return []
223
222
  tree = context.tree
224
- if tree is None or _has_wildcard_import(tree, node_index=context.node_index):
223
+ if tree is None or context.has_wildcard_import:
225
224
  return []
226
225
 
227
226
  imports = context.imports
@@ -311,13 +310,6 @@ def _is_excluded_path(path: Path) -> bool:
311
310
  )
312
311
 
313
312
 
314
- def _has_wildcard_import(tree: ast.Module, *, node_index: NodeIndex | None = None) -> bool:
315
- return any(
316
- isinstance(node, ast.ImportFrom) and any(alias.name == "*" for alias in node.names)
317
- for node in walk_ast(tree, index=node_index)
318
- )
319
-
320
-
321
313
  def _boundary_nodes(
322
314
  tree: ast.Module,
323
315
  imports: ImportIndex,
@@ -23,7 +23,6 @@ if TYPE_CHECKING:
23
23
  from collections.abc import Iterator
24
24
 
25
25
  from sarj_python_lint._file_context import PythonFileContext
26
- from sarj_python_lint.rules._ast_index import NodeIndex
27
26
  from sarj_python_lint.rules._imports import ImportIndex
28
27
 
29
28
 
@@ -99,7 +98,7 @@ class PreferNonNullableCollection(Rule):
99
98
  if is_test_path(path) or is_test_support_path(path) or context.generated:
100
99
  return []
101
100
  tree = context.tree
102
- if tree is None or _has_wildcard_import(tree, node_index=context.node_index):
101
+ if tree is None or context.has_wildcard_import:
103
102
  return []
104
103
 
105
104
  imports = context.imports
@@ -125,13 +124,6 @@ class PreferNonNullableCollection(Rule):
125
124
  return sorted(diagnostics, key=lambda diagnostic: (diagnostic.line, diagnostic.col))
126
125
 
127
126
 
128
- def _has_wildcard_import(tree: ast.Module, *, node_index: NodeIndex | None = None) -> bool:
129
- return any(
130
- isinstance(node, ast.ImportFrom) and any(alias.name == "*" for alias in node.names)
131
- for node in walk_ast(tree, index=node_index)
132
- )
133
-
134
-
135
127
  def _eligible_functions(tree: ast.Module, imports: ImportIndex) -> list[ast.FunctionDef | ast.AsyncFunctionDef]:
136
128
  functions: list[ast.FunctionDef | ast.AsyncFunctionDef] = []
137
129
  for statement in tree.body:
@@ -61,7 +61,10 @@ class RedundantTestConstructorForwarder(ProjectRule):
61
61
  example_id=name,
62
62
  title=title,
63
63
  outcome=outcome,
64
- files=(ExampleFile.python("tests/test_record.py", source),),
64
+ files=(
65
+ ExampleFile.python("tests/__init__.py", "# Example test package.\n"),
66
+ ExampleFile.python("tests/test_record.py", source),
67
+ ),
65
68
  focus_path=PurePosixPath("tests/test_record.py"),
66
69
  expected_count=count,
67
70
  public=True,
@@ -60,6 +60,7 @@ class RepeatedUnimplementedTestContract(ProjectRule):
60
60
  title="Two test fakes repeat one unconfigured operation",
61
61
  outcome=ExampleOutcome.MATCH,
62
62
  files=(
63
+ ExampleFile.python("tests/__init__.py", "# Example test package.\n"),
63
64
  ExampleFile.python(
64
65
  "tests/test_store.py",
65
66
  "from abc import ABC, abstractmethod\nclass Store(ABC):\n @abstractmethod\n def read(self, key: str) -> str:\n pass\nclass First(Store):\n def read(self, key: str) -> str:\n raise NotImplementedError\nclass Second(Store):\n def read(self, key: str) -> str:\n raise NotImplementedError\n",
@@ -74,6 +75,7 @@ class RepeatedUnimplementedTestContract(ProjectRule):
74
75
  title="Configured fake behavior belongs to the leaf",
75
76
  outcome=ExampleOutcome.NO_MATCH,
76
77
  files=(
78
+ ExampleFile.python("tests/__init__.py", "# Example test package.\n"),
77
79
  ExampleFile.python(
78
80
  "tests/test_store.py",
79
81
  "from abc import ABC, abstractmethod\nclass Store(ABC):\n @abstractmethod\n def read(self, key: str) -> str:\n pass\nclass First(Store):\n def read(self, key: str) -> str:\n raise NotImplementedError\nclass Second(Store):\n def read(self, key: str) -> str:\n return key\n",