sarj-python-lint 1.14.1__tar.gz → 1.14.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 (171) hide show
  1. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_registry.py +2 -0
  4. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_injected_module_loader.py +14 -10
  5. sarj_python_lint-1.14.3/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +168 -0
  6. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +78 -16
  7. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +13 -12
  8. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/.gitignore +0 -0
  9. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/LICENSE +0 -0
  10. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/README.md +0 -0
  11. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/__init__.py +0 -0
  12. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/__main__.py +0 -0
  13. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/_analysis_session.py +0 -0
  14. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/_file_context.py +0 -0
  15. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/_filesystem.py +0 -0
  16. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/_python_target.py +0 -0
  17. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  18. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/_secret_names.py +0 -0
  19. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/_version.py +0 -0
  20. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/benchmark.py +0 -0
  21. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/json_boundary.py +0 -0
  22. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/py.typed +0 -0
  23. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/ratchet.py +0 -0
  24. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rule_base.py +0 -0
  25. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/__init__.py +0 -0
  26. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  27. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  28. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  29. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_comments.py +0 -0
  30. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  31. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  32. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  33. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  34. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_first_party.py +0 -0
  35. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  36. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_imports.py +0 -0
  37. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_logging.py +0 -0
  38. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  39. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_paths.py +0 -0
  40. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_project_index.py +0 -0
  41. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  42. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_pytest.py +0 -0
  43. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  44. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  45. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_sql.py +0 -0
  46. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  47. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  48. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  49. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  50. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  51. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  52. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  53. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/discourage_nullable_constructor_parameters.py +0 -0
  54. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  55. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  56. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  57. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  58. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  59. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  60. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  61. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  62. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  63. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  64. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  65. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  66. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  67. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  68. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  69. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  70. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  71. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  72. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  73. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  74. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  75. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  76. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  77. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  78. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  79. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  80. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  81. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  82. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  83. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  84. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  85. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  86. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  87. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  88. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  89. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  90. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  91. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  92. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  93. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  94. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  95. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  96. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  97. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  98. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  99. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  100. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  101. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  102. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  103. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  104. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  105. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  106. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  107. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  108. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  109. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  110. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  111. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  112. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  113. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  114. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  115. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  116. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  117. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  118. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  119. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  120. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  121. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  122. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  123. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  124. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  125. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  126. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  127. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  128. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  129. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  130. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  131. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  132. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  133. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  134. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  135. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  136. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  137. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  138. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  139. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  140. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  141. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  142. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  143. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  144. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  145. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  146. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  147. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  148. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  149. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  150. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  151. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  152. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  153. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  154. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  155. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  156. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  157. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  158. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  159. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  160. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  161. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  162. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/stepdown.py +0 -0
  163. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  164. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  165. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  166. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  167. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  168. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  169. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  170. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.3}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  171. {sarj_python_lint-1.14.1 → sarj_python_lint-1.14.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.14.1
3
+ Version: 1.14.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/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sarj-python-lint"
3
- version = "1.14.1"
3
+ version = "1.14.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" }]
@@ -88,6 +88,7 @@ from sarj_python_lint.rules.no_unique_violation_message_match import (
88
88
  NoUniqueViolationMessageMatch,
89
89
  )
90
90
  from sarj_python_lint.rules.no_unnecessary_docstring import NoUnnecessaryDocstring
91
+ from sarj_python_lint.rules.no_unused_underscored_keyword_parameter import NoUnusedUnderscoredKeywordParameter
91
92
  from sarj_python_lint.rules.no_unused_value_marker import NoUnusedValueMarker
92
93
  from sarj_python_lint.rules.no_vague_suppression_description import (
93
94
  NoVagueSuppressionDescription,
@@ -306,6 +307,7 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
306
307
  NoTypedDocSections.id: NoTypedDocSections,
307
308
  NoUnnecessaryDocstring.id: NoUnnecessaryDocstring,
308
309
  NoUnusedValueMarker.id: NoUnusedValueMarker,
310
+ NoUnusedUnderscoredKeywordParameter.id: NoUnusedUnderscoredKeywordParameter,
309
311
  PreferNominalIdTypes.id: PreferNominalIdTypes,
310
312
  NoAnyMappingTypes.id: NoAnyMappingTypes,
311
313
  NoUniqueViolationMessageMatch.id: NoUniqueViolationMessageMatch,
@@ -185,17 +185,21 @@ def _shadowed(default: ast.expr, function: ast.AST, context: PythonFileContext)
185
185
  parent = context.parents.get(child)
186
186
  class_scope_visible = True
187
187
  while parent is not None:
188
- if isinstance(parent, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)):
189
- return True
190
- if isinstance(parent, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda)):
191
- if _in_scope_body(parent, child):
192
- if root.id in scope_bindings(parent):
193
- return True
194
- class_scope_visible = False
195
- elif isinstance(parent, ast.ClassDef) and _in_scope_body(parent, child):
196
- if class_scope_visible and _class_prefix_shadows(parent, child, root.id):
188
+ match parent:
189
+ case ast.ListComp() | ast.SetComp() | ast.DictComp() | ast.GeneratorExp():
197
190
  return True
198
- class_scope_visible = False
191
+ case ast.FunctionDef() | ast.AsyncFunctionDef() | ast.Lambda():
192
+ if _in_scope_body(parent, child):
193
+ if root.id in scope_bindings(parent):
194
+ return True
195
+ class_scope_visible = False
196
+ case ast.ClassDef():
197
+ if _in_scope_body(parent, child):
198
+ if class_scope_visible and _class_prefix_shadows(parent, child, root.id):
199
+ return True
200
+ class_scope_visible = False
201
+ case _:
202
+ pass
199
203
  child = parent
200
204
  parent = context.parents.get(parent)
201
205
  return False
@@ -0,0 +1,168 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from itertools import chain
5
+ from pathlib import PurePosixPath
6
+ from typing import TYPE_CHECKING, ClassVar, final, override
7
+
8
+ from sarj_python_lint.rule_base import (
9
+ AutofixPolicy,
10
+ Diagnostic,
11
+ ExampleFile,
12
+ ExampleOutcome,
13
+ Rule,
14
+ RuleCategory,
15
+ RuleDocumentation,
16
+ RuleExample,
17
+ Severity,
18
+ is_suppressed,
19
+ )
20
+
21
+
22
+ if TYPE_CHECKING:
23
+ from sarj_python_lint._file_context import PythonFileContext
24
+
25
+
26
+ @final
27
+ class NoUnusedUnderscoredKeywordParameter(Rule):
28
+ id = "no-unused-underscored-keyword-parameter"
29
+ code = "SARJ475"
30
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
31
+ default_level=Severity.WARNING,
32
+ summary="Remove unused underscore-prefixed keyword-only parameters instead of hiding them from lint.",
33
+ rationale=(
34
+ "A keyword-only parameter requires callers to use its declared name. Prefixing an unused input with an "
35
+ "underscore silences unused-argument lint while retaining an input whose value has no effect. Removing "
36
+ "the dead input and its callers keeps the callable's interface honest."
37
+ ),
38
+ remediation=(
39
+ "Remove the unused keyword-only parameter and update callers together. If an external contract requires "
40
+ "the input, preserve its required spelling and use a narrow, reasoned unused-argument suppression."
41
+ ),
42
+ category=RuleCategory.MAINTAINABILITY,
43
+ autofix=AutofixPolicy.NONE,
44
+ limitations=(
45
+ "Only underscore-prefixed keyword-only declarations are checked; positional and variadic callback slots are excluded.",
46
+ "Decorated functions other than unshadowed builtin staticmethod/classmethod, inherited methods, and intentional stubs are excluded.",
47
+ "Any reference within the body counts as use, including nested scopes; shadowing and reassignment can produce false negatives.",
48
+ "Functions accessing locals, vars, eval, or exec through recognized builtin bindings are excluded.",
49
+ "Call sites, lambda parameters, unknown dynamic reflection, and cross-module callback contracts are not resolved.",
50
+ "Generated and vendored files are excluded; test source is checked.",
51
+ ),
52
+ examples=(
53
+ RuleExample(
54
+ example_id="discarded-keyword-input",
55
+ title="An ignored keyword input is kept just to silence unused-argument lint",
56
+ outcome=ExampleOutcome.MATCH,
57
+ files=(ExampleFile.python("app/sink.py", "def emit(*, _email=None):\n return 'ready'\n"),),
58
+ focus_path=PurePosixPath("app/sink.py"),
59
+ expected_count=1,
60
+ public=True,
61
+ ),
62
+ RuleExample(
63
+ example_id="removed-keyword-input",
64
+ title="The callable exposes only inputs it uses",
65
+ outcome=ExampleOutcome.NO_MATCH,
66
+ files=(ExampleFile.python("app/sink.py", "def emit():\n return 'ready'\n"),),
67
+ focus_path=PurePosixPath("app/sink.py"),
68
+ expected_count=0,
69
+ public=True,
70
+ ),
71
+ ),
72
+ )
73
+ description = documentation.summary
74
+
75
+ @override
76
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
77
+ if context.generated or "_" not in context.source or context.tree is None:
78
+ return []
79
+ findings: list[Diagnostic] = []
80
+ for function in context.nodes(ast.FunctionDef, ast.AsyncFunctionDef):
81
+ for argument in _unused_parameters(context, function):
82
+ if is_suppressed(context.source_lines, argument.lineno, self.code):
83
+ continue
84
+ findings.append(
85
+ Diagnostic(
86
+ path=context.path,
87
+ line=argument.lineno,
88
+ col=argument.col_offset + 1,
89
+ code=self.code,
90
+ severity=Severity.WARNING,
91
+ message=(
92
+ f"keyword-only parameter `{argument.arg}` is never referenced; remove the unused input "
93
+ "and its callers instead of hiding it from unused-argument lint"
94
+ ),
95
+ )
96
+ )
97
+ return sorted(findings, key=lambda item: (item.line, item.col))
98
+
99
+
100
+ def _unused_parameters(
101
+ context: PythonFileContext, function: ast.FunctionDef | ast.AsyncFunctionDef
102
+ ) -> tuple[ast.arg, ...]:
103
+ candidates = [argument for argument in function.args.kwonlyargs if argument.arg.startswith("_")]
104
+ if not candidates or _has_contract(context, function) or _is_stub(function):
105
+ return ()
106
+ body = list(chain.from_iterable(ast.walk(statement) for statement in function.body))
107
+ if _reads_dynamic_locals(context, body):
108
+ return ()
109
+ references = {node.id for node in body if isinstance(node, ast.Name)}
110
+ return tuple(argument for argument in candidates if argument.arg not in references)
111
+
112
+
113
+ def _has_contract(context: PythonFileContext, function: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
114
+ parent = context.parents.get(function)
115
+ while parent is not None and not isinstance(
116
+ parent, ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef | ast.Module
117
+ ):
118
+ parent = context.parents.get(parent)
119
+ if isinstance(parent, ast.ClassDef) and parent.bases:
120
+ return True
121
+ return any(
122
+ not (
123
+ isinstance(decorator, ast.Name)
124
+ and decorator.id in {"staticmethod", "classmethod"}
125
+ and context.imports.builtin_is_unshadowed(decorator.id)
126
+ )
127
+ for decorator in function.decorator_list
128
+ )
129
+
130
+
131
+ def _is_stub(function: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
132
+ body = function.body
133
+ if (
134
+ body
135
+ and isinstance(body[0], ast.Expr)
136
+ and isinstance(body[0].value, ast.Constant)
137
+ and isinstance(body[0].value.value, str)
138
+ ):
139
+ body = body[1:]
140
+ if len(body) != 1:
141
+ return False
142
+ match body[0]:
143
+ case ast.Pass() | ast.Return(value=None) | ast.Return(value=ast.Constant(value=None)):
144
+ return True
145
+ case ast.Expr(value=ast.Constant(value=value)):
146
+ return value is Ellipsis
147
+ case ast.Raise(exc=exception):
148
+ if isinstance(exception, ast.Call):
149
+ exception = exception.func
150
+ return isinstance(exception, ast.Name) and exception.id == "NotImplementedError"
151
+ case _:
152
+ return False
153
+
154
+
155
+ def _reads_dynamic_locals(context: PythonFileContext, body: list[ast.AST]) -> bool:
156
+ return any(
157
+ isinstance(node, ast.expr)
158
+ and (
159
+ (
160
+ isinstance(node, ast.Name)
161
+ and node.id in {"locals", "vars", "eval", "exec"}
162
+ and context.module_imports.builtin_is_unshadowed(node.id)
163
+ )
164
+ or context.module_imports.resolved_symbol(node, sources=frozenset({"builtins"}))
165
+ in {"locals", "vars", "eval", "exec"}
166
+ )
167
+ for node in body
168
+ )
@@ -100,6 +100,7 @@ class PreferMatchTypeDispatch(Rule):
100
100
  autofix=AutofixPolicy.NONE,
101
101
  limitations=(
102
102
  "General dispatch requires three or more adjacent, unguarded `isinstance` branches over the same simple name. Two arms are checked only for an exact `ast.Name.id` / `ast.Attribute.attr` projection followed by a None-returning fallback.",
103
+ "A terminating sibling prefix may precede a two-arm if/elif tail whose bodies can fall through. Preserve nested checks inside case bodies rather than moving them into case guards.",
103
104
  "The checked types must be unshadowed builtins, unshadowed module-local classes, or proven stdlib ast classes; unresolved imports, runtime type groups, repeated type references, generated files, and non-terminating sibling checks are excluded.",
104
105
  "Nested attribute-validation guards are excluded: converting them to keyword class patterns can turn attribute errors into match fallthrough, and an imported isinstance operand may be a runtime tuple rather than a class.",
105
106
  "A terminal-looking context-manager body does not prove a sibling branch terminates: exceptions can be suppressed. An unconditional return or raise after the context manager remains eligible.",
@@ -194,6 +195,36 @@ class PreferMatchTypeDispatch(Rule):
194
195
  expected_count=0,
195
196
  public=True,
196
197
  ),
198
+ RuleExample(
199
+ example_id="terminal-prefix-with-conditional-tail",
200
+ title="A terminating prefix and conditional if/elif tail dispatch on the same AST node",
201
+ outcome=ExampleOutcome.MATCH,
202
+ scenario="mixed-type-dispatch",
203
+ files=(
204
+ ExampleFile.python(
205
+ "app/bindings.py",
206
+ "import ast\ndef has_binding(node, name):\n if isinstance(node, ast.ListComp):\n return True\n if isinstance(node, ast.FunctionDef):\n if name in bindings(node):\n return True\n elif isinstance(node, ast.ClassDef):\n if name in bindings(node):\n return True\n return False\n",
207
+ ),
208
+ ),
209
+ focus_path=PurePosixPath("app/bindings.py"),
210
+ expected_count=1,
211
+ public=True,
212
+ ),
213
+ RuleExample(
214
+ example_id="class-patterns-with-nested-checks",
215
+ title="Class patterns preserve the nested checks within their case bodies",
216
+ outcome=ExampleOutcome.NO_MATCH,
217
+ scenario="mixed-type-dispatch",
218
+ files=(
219
+ ExampleFile.python(
220
+ "app/bindings.py",
221
+ "import ast\ndef has_binding(node, name):\n match node:\n case ast.ListComp():\n return True\n case ast.FunctionDef():\n if name in bindings(node):\n return True\n case ast.ClassDef():\n if name in bindings(node):\n return True\n return False\n",
222
+ ),
223
+ ),
224
+ focus_path=PurePosixPath("app/bindings.py"),
225
+ expected_count=0,
226
+ public=True,
227
+ ),
197
228
  ),
198
229
  )
199
230
  description: str = documentation.summary
@@ -330,24 +361,23 @@ def _sibling_findings(
330
361
  def collect_block(statements: list[ast.stmt]) -> None:
331
362
  index = 0
332
363
  while index < len(statements):
333
- run: list[ast.If] = []
334
- cursor = index
335
- subject: str | None = None
336
- seen: set[str] = set()
337
- while cursor < len(statements):
338
- statement = statements[cursor]
339
- if not isinstance(statement, ast.If) or statement.orelse or not _body_terminates(statement.body):
340
- break
341
- branch = _type_branch(statement.test, imports, local_classes, unsafe_bindings)
342
- if branch is None or (subject is not None and branch.subject != subject) or bool(seen & branch.types):
343
- break
344
- run.append(statement)
345
- subject = branch.subject
346
- seen.update(branch.types)
347
- cursor += 1
364
+ run = _terminal_prefix(statements, index, imports, local_classes, unsafe_bindings)
365
+ cursor = index + len(run)
366
+ if run and cursor < len(statements):
367
+ tail_statement = statements[cursor]
368
+ if isinstance(tail_statement, ast.If):
369
+ tail = _two_arm_ladder(tail_statement)
370
+ if tail and _dispatch([*run, *tail], imports, local_classes, unsafe_bindings) is not None:
371
+ run.extend(tail)
372
+ cursor += 1
348
373
  dispatch = _dispatch(run, imports, local_classes, unsafe_bindings)
349
374
  if dispatch is not None:
350
- findings.append(_diagnostic(path, code, run[0], dispatch, "terminating isinstance sequence"))
375
+ shape = (
376
+ "terminating isinstance prefix with if/elif tail"
377
+ if run[-2].orelse
378
+ else "terminating isinstance sequence"
379
+ )
380
+ findings.append(_diagnostic(path, code, run[0], dispatch, shape))
351
381
  index = max(cursor, index + 1)
352
382
 
353
383
  for owner in all_nodes:
@@ -356,6 +386,38 @@ def _sibling_findings(
356
386
  return findings
357
387
 
358
388
 
389
+ def _terminal_prefix(
390
+ statements: list[ast.stmt],
391
+ index: int,
392
+ imports: ImportIndex,
393
+ local_classes: frozenset[str],
394
+ unsafe_bindings: frozenset[str],
395
+ ) -> list[ast.If]:
396
+ run: list[ast.If] = []
397
+ subject: str | None = None
398
+ seen: set[str] = set()
399
+ for cursor in range(index, len(statements)):
400
+ statement = statements[cursor]
401
+ if not isinstance(statement, ast.If) or statement.orelse or not _body_terminates(statement.body):
402
+ break
403
+ branch = _type_branch(statement.test, imports, local_classes, unsafe_bindings)
404
+ if branch is None or (subject is not None and branch.subject != subject) or bool(seen & branch.types):
405
+ break
406
+ run.append(statement)
407
+ subject = branch.subject
408
+ seen.update(branch.types)
409
+ return run
410
+
411
+
412
+ def _two_arm_ladder(statement: ast.If) -> tuple[ast.If, ast.If] | None:
413
+ if len(statement.orelse) != 1 or not isinstance(statement.orelse[0], ast.If):
414
+ return None
415
+ second = statement.orelse[0]
416
+ if len(second.orelse) == 1 and isinstance(second.orelse[0], ast.If):
417
+ return None
418
+ return statement, second
419
+
420
+
359
421
  def _dispatch(
360
422
  branches: list[ast.If],
361
423
  imports: ImportIndex,
@@ -324,18 +324,19 @@ def _module_rebound_names(tree: ast.Module) -> frozenset[str]:
324
324
  def _suite_rebound_names(statements: list[ast.stmt]) -> frozenset[str]:
325
325
  names: set[str] = set()
326
326
  for statement in statements:
327
- if isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef)):
328
- continue
329
- if isinstance(statement, ast.ClassDef):
330
- names.add(statement.name)
331
- elif isinstance(statement, ast.Import | ast.ImportFrom):
332
- names.update(alias.asname or alias.name.partition(".")[0] for alias in statement.names)
333
- else:
334
- names.update(
335
- node.id
336
- for node in walk_ast(statement)
337
- if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store)
338
- )
327
+ match statement:
328
+ case ast.FunctionDef() | ast.AsyncFunctionDef():
329
+ continue
330
+ case ast.ClassDef():
331
+ names.add(statement.name)
332
+ case ast.Import() | ast.ImportFrom():
333
+ names.update(alias.asname or alias.name.partition(".")[0] for alias in statement.names)
334
+ case _:
335
+ names.update(
336
+ node.id
337
+ for node in walk_ast(statement)
338
+ if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store)
339
+ )
339
340
  return frozenset(names)
340
341
 
341
342