sarj-python-lint 1.15.2__tar.gz → 1.16.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 (174) hide show
  1. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_registry.py +2 -0
  4. sarj_python_lint-1.16.0/src/sarj_python_lint/rules/no_broad_keyword_capture.py +361 -0
  5. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/.gitignore +0 -0
  6. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/LICENSE +0 -0
  7. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/README.md +0 -0
  8. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/__init__.py +0 -0
  9. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/__main__.py +0 -0
  10. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  11. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/_file_context.py +0 -0
  12. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/_filesystem.py +0 -0
  13. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/_python_target.py +0 -0
  14. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  15. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/_secret_names.py +0 -0
  16. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/_version.py +0 -0
  17. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/benchmark.py +0 -0
  18. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/json_boundary.py +0 -0
  19. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/py.typed +0 -0
  20. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/ratchet.py +0 -0
  21. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rule_base.py +0 -0
  22. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  23. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  24. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  25. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  26. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  27. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  28. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  29. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  30. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  31. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  32. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  33. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  34. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  35. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  36. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  37. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  38. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  39. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  40. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  41. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  42. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  43. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  44. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  45. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  46. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  47. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  48. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  49. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  50. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  51. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/discourage_nullable_constructor_parameters.py +0 -0
  52. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  53. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  54. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  55. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  56. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  57. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  58. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  59. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  60. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  61. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  62. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  63. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  64. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  65. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  66. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  67. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  68. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  69. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  70. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  71. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  72. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  73. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  74. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  75. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  76. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  77. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  78. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  79. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  80. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  81. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  82. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  83. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  84. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  85. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  86. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  87. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  88. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  89. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  90. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  91. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  92. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  93. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  94. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  95. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  96. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  97. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  98. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  99. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  100. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  101. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  102. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  103. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  104. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  105. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  106. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  107. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  108. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  109. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  110. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  111. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  112. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  113. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  114. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  115. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  116. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  117. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  118. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  119. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  120. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  121. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  122. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  123. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  124. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  125. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  126. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  127. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  128. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  129. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  130. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  131. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  132. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  133. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  134. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  135. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  136. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  137. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  138. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  139. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  140. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  141. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  142. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  143. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  144. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  145. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  146. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  147. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  148. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  149. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  150. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  151. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  152. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  153. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  154. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  155. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  156. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  157. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  158. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  159. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  160. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  161. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  162. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  163. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  164. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  165. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  166. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  167. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  168. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  169. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  170. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  171. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  172. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  173. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  174. {sarj_python_lint-1.15.2 → sarj_python_lint-1.16.0}/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.15.2
3
+ Version: 1.16.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.15.2"
3
+ version = "1.16.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" }]
@@ -33,6 +33,7 @@ from sarj_python_lint.rules.no_analytical_aggregation_in_postgres_store import (
33
33
  NoAnalyticalAggregationInPostgresStore,
34
34
  )
35
35
  from sarj_python_lint.rules.no_any_mapping_types import NoAnyMappingTypes
36
+ from sarj_python_lint.rules.no_broad_keyword_capture import NoBroadKeywordCapture
36
37
  from sarj_python_lint.rules.no_comment_cruft import NoCommentCruft
37
38
  from sarj_python_lint.rules.no_conftest_test_module_import import NoConftestTestModuleImport
38
39
  from sarj_python_lint.rules.no_copied_inherited_docstring import NoCopiedInheritedDocstring
@@ -297,6 +298,7 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
297
298
  NoDuplicateDunderAllEntry.id: NoDuplicateDunderAllEntry,
298
299
  NoDunderAll.id: NoDunderAll,
299
300
  NoDeleteStatement.id: NoDeleteStatement,
301
+ NoBroadKeywordCapture.id: NoBroadKeywordCapture,
300
302
  NoCopiedInheritedDocstring.id: NoCopiedInheritedDocstring,
301
303
  RedundantClassDocstring.id: RedundantClassDocstring,
302
304
  RedundantModuleDocstring.id: RedundantModuleDocstring,
@@ -0,0 +1,361 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from pathlib import PurePosixPath
5
+ from typing import TYPE_CHECKING, ClassVar, final
6
+
7
+ from sarj_python_lint.rule_base import (
8
+ AutofixPolicy,
9
+ Diagnostic,
10
+ ExampleFile,
11
+ ExampleOutcome,
12
+ Rule,
13
+ RuleCategory,
14
+ RuleDocumentation,
15
+ RuleExample,
16
+ Severity,
17
+ is_suppressed,
18
+ )
19
+ from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics, scope_bound_names
20
+ from sarj_python_lint.rules._ast_index import walk
21
+
22
+
23
+ if TYPE_CHECKING:
24
+ from sarj_python_lint._file_context import PythonFileContext
25
+
26
+
27
+ _MIN_OVERLOAD_DECLARATIONS = 2
28
+ _OVERLOAD_DECORATORS = frozenset({"typing.overload", "typing_extensions.overload"})
29
+ _KNOWN_DECORATORS = _OVERLOAD_DECORATORS | {
30
+ "builtins.classmethod",
31
+ "builtins.staticmethod",
32
+ "abc.abstractmethod",
33
+ "typing.final",
34
+ "typing.override",
35
+ "typing_extensions.final",
36
+ "typing_extensions.override",
37
+ }
38
+
39
+
40
+ @final
41
+ class NoBroadKeywordCapture(Rule):
42
+ id = "no-broad-keyword-capture"
43
+ code = "SARJ477"
44
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
45
+ default_level=Severity.WARNING,
46
+ summary="Prefer precise keyword interfaces over missing, Any- or object-valued captures.",
47
+ rationale=(
48
+ "Replacing a fixed typed interface with broad **kwargs can erase required keyword names and per-key "
49
+ "value types, making test doubles accept calls the real implementation rejects. object remains a safe "
50
+ "typed top type, and deliberately open data interfaces can be valid; this warning requests interface "
51
+ "review rather than proving every broad capture is defective."
52
+ ),
53
+ remediation=(
54
+ "Keep explicit typed parameters at fixed interfaces. Preserve required keyword names with named unused "
55
+ "local bindings. Use ParamSpec for forwarding, Unpack[TypedDict] for named schemas, or a precise "
56
+ "homogeneous value type. Document genuinely open interfaces with an exact-code reasoned suppression."
57
+ ),
58
+ category=RuleCategory.MAINTAINABILITY,
59
+ autofix=AutofixPolicy.NONE,
60
+ limitations=(
61
+ "This is an advisory interface policy, not proof of incorrect behavior or a universal ban on the object type.",
62
+ "Reports missing, Any- or object-valued keyword captures whether discarded, read or forwarded.",
63
+ "Precise or unresolved decorated signature-declaration families and functions with unresolved decorator effects are excluded; known classmethod/staticmethod/abstractmethod/final/override declarations remain eligible.",
64
+ "Homogeneous keyword types constrain values but allow arbitrary names; ParamSpec preserves only its target's existing contract.",
65
+ "Generated/vendor source and stubs are excluded. Unresolved imports, wildcard imports and shadowed annotation provenance are conservative.",
66
+ "Cross-file aliases, external stubs, generated signatures and runtime reflection are not resolved; transparent annotation metadata and ambiguous decorated declaration families can cause conservative false negatives.",
67
+ "No autofix can infer the intended contract. Dynamic payload/formatting APIs can intentionally need an exact-code exception.",
68
+ ),
69
+ examples=(
70
+ RuleExample(
71
+ example_id="discarded-session-keywords",
72
+ title="An ignored catch-all weakens a fake's contract",
73
+ outcome=ExampleOutcome.MATCH,
74
+ files=(
75
+ ExampleFile.python(
76
+ "tests/fakes/session.py",
77
+ "class Session:\n async def start(self, **_kwargs: object) -> None:\n self.start_count += 1\n",
78
+ ),
79
+ ),
80
+ focus_path=PurePosixPath("tests/fakes/session.py"),
81
+ expected_count=1,
82
+ public=True,
83
+ ),
84
+ RuleExample(
85
+ example_id="typed-unused-keywords",
86
+ title="Preserve named typed keywords with a named unused binding",
87
+ outcome=ExampleOutcome.NO_MATCH,
88
+ files=(
89
+ ExampleFile.python(
90
+ "tests/fakes/session.py",
91
+ "class Session:\n async def start(self, *, room: Room, agent: Agent) -> None:\n _unused_inputs = room, agent\n self.start_count += 1\n",
92
+ ),
93
+ ),
94
+ focus_path=PurePosixPath("tests/fakes/session.py"),
95
+ expected_count=0,
96
+ public=True,
97
+ ),
98
+ RuleExample(
99
+ example_id="broad-forwarding-arguments",
100
+ title="Forwarding broad arguments still loses the callable contract",
101
+ outcome=ExampleOutcome.MATCH,
102
+ files=(
103
+ ExampleFile.python(
104
+ "app/adapter.py",
105
+ "def wrapper(*args: object, **kwargs: object):\n return target(*args, **kwargs)\n",
106
+ ),
107
+ ),
108
+ focus_path=PurePosixPath("app/adapter.py"),
109
+ expected_count=1,
110
+ public=True,
111
+ scenario="forwarding",
112
+ ),
113
+ RuleExample(
114
+ example_id="typed-forwarding-arguments",
115
+ title="ParamSpec preserves the forwarded callable contract",
116
+ outcome=ExampleOutcome.NO_MATCH,
117
+ files=(
118
+ ExampleFile.python(
119
+ "app/adapter.py",
120
+ "from collections.abc import Callable\ndef wrapper[**P, R](target: Callable[P, R], *args: P.args, **kwargs: P.kwargs) -> R:\n return target(*args, **kwargs)\n",
121
+ ),
122
+ ),
123
+ focus_path=PurePosixPath("app/adapter.py"),
124
+ expected_count=0,
125
+ public=True,
126
+ scenario="forwarding",
127
+ ),
128
+ RuleExample(
129
+ example_id="open-template-fields",
130
+ title="A deliberately open formatting interface still requests policy review",
131
+ outcome=ExampleOutcome.MATCH,
132
+ files=(
133
+ ExampleFile.python(
134
+ "app/templates.py",
135
+ "def render(template: str, **values: object) -> str:\n return template.format(**values)\n",
136
+ ),
137
+ ),
138
+ focus_path=PurePosixPath("app/templates.py"),
139
+ expected_count=1,
140
+ public=True,
141
+ scenario="dynamic-boundary",
142
+ ),
143
+ RuleExample(
144
+ example_id="documented-open-template-fields",
145
+ title="Document why template fields require an open object-valued interface",
146
+ outcome=ExampleOutcome.NO_MATCH,
147
+ files=(
148
+ ExampleFile.python(
149
+ "app/templates.py",
150
+ "def render(template: str, **values: object) -> str: # sarj-noqa: SARJ477 — template supplies dynamic field names and object formatting\n return template.format(**values)\n",
151
+ ),
152
+ ),
153
+ focus_path=PurePosixPath("app/templates.py"),
154
+ expected_count=0,
155
+ public=True,
156
+ scenario="dynamic-boundary",
157
+ ),
158
+ ),
159
+ )
160
+ description = documentation.summary
161
+
162
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
163
+ if "*" not in context.source or context.generated or context.path.suffix == ".pyi":
164
+ return []
165
+ tree = context.tree
166
+ if tree is None:
167
+ return []
168
+ semantics = AnnotationSemantics.from_tree(tree)
169
+ overloads: set[ast.FunctionDef | ast.AsyncFunctionDef] | None = None
170
+ findings: list[Diagnostic] = []
171
+ for function in context.nodes(ast.FunctionDef, ast.AsyncFunctionDef):
172
+ argument = function.args.kwarg
173
+ if argument is None:
174
+ continue
175
+ if not _is_broad(argument.annotation, semantics):
176
+ continue
177
+ if _uncertain_annotation(argument.annotation, function, context, semantics):
178
+ continue
179
+ if _unknown_decorator(function, context, semantics):
180
+ continue
181
+ if overloads is None:
182
+ overloads = _overload_implementations(context, semantics)
183
+ if function in overloads:
184
+ continue
185
+ if is_suppressed(context.source_lines, argument.lineno, self.code) or is_suppressed(
186
+ context.source_lines, function.lineno, self.code
187
+ ):
188
+ continue
189
+ findings.append(
190
+ Diagnostic(
191
+ path=context.path,
192
+ line=argument.lineno,
193
+ col=argument.col_offset + 1,
194
+ code=self.code,
195
+ severity=Severity.WARNING,
196
+ message=(
197
+ f"keyword capture `{argument.arg}` has no declared names or narrow value type; "
198
+ "prefer an explicit typed interface or document a deliberately open contract."
199
+ ),
200
+ )
201
+ )
202
+ return sorted(findings, key=lambda finding: (finding.line, finding.col))
203
+
204
+
205
+ def _is_broad(annotation: ast.expr | None, semantics: AnnotationSemantics) -> bool:
206
+ if annotation is None:
207
+ return True
208
+ return any(
209
+ (isinstance(member, ast.Name) and member.id == "object" and semantics.imports.builtin_is_unshadowed("object"))
210
+ or semantics.imports.resolved_qualified_name(member)
211
+ in {"typing.Any", "typing_extensions.Any", "builtins.object"}
212
+ for member in semantics.transparent_members(annotation)
213
+ )
214
+
215
+
216
+ def _shadowed_annotation(
217
+ annotation: ast.expr | None,
218
+ function: ast.FunctionDef | ast.AsyncFunctionDef,
219
+ context: PythonFileContext,
220
+ semantics: AnnotationSemantics,
221
+ ) -> bool:
222
+ names = _uses_names(annotation, semantics)
223
+ if names & _type_parameter_names(function):
224
+ return True
225
+ parent = context.parents.get(function)
226
+ while parent is not None:
227
+ if isinstance(parent, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)) and names & _scope_bindings(
228
+ parent
229
+ ):
230
+ return True
231
+ parent = context.parents.get(parent)
232
+ return False
233
+
234
+
235
+ def _scope_bindings(owner: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) -> set[str]:
236
+ bound = scope_bound_names(owner.body) | _type_parameter_names(owner)
237
+ if isinstance(owner, (ast.FunctionDef, ast.AsyncFunctionDef)):
238
+ args = owner.args
239
+ bound.update(arg.arg for arg in [*args.posonlyargs, *args.args, *args.kwonlyargs])
240
+ bound.update(arg.arg for arg in (args.vararg, args.kwarg) if arg is not None)
241
+ return bound
242
+
243
+
244
+ def _has_wildcard_import(context: PythonFileContext) -> bool:
245
+ return any(alias.name == "*" for node in context.nodes(ast.ImportFrom) for alias in node.names)
246
+
247
+
248
+ def _uncertain_annotation(
249
+ annotation: ast.expr | None,
250
+ function: ast.FunctionDef | ast.AsyncFunctionDef,
251
+ context: PythonFileContext,
252
+ semantics: AnnotationSemantics,
253
+ ) -> bool:
254
+ if annotation is None:
255
+ return False
256
+ if _has_wildcard_import(context):
257
+ return True
258
+ if context.tree is not None:
259
+ unknown = scope_bound_names(context.tree.body) - set(semantics.imports.bindings) - set(semantics.aliases)
260
+ if _uses_names(annotation, semantics) & unknown:
261
+ return True
262
+ return _shadowed_annotation(annotation, function, context, semantics)
263
+
264
+
265
+ def _type_parameter_names(owner: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) -> set[str]:
266
+ return {
267
+ parameter.name
268
+ for parameter in owner.type_params
269
+ if isinstance(parameter, (ast.TypeVar, ast.TypeVarTuple, ast.ParamSpec))
270
+ }
271
+
272
+
273
+ def _uses_names(expression: ast.expr | None, semantics: AnnotationSemantics) -> set[str]:
274
+ parsed = semantics.parse(expression)
275
+ return {node.id for node in walk(parsed) if isinstance(node, ast.Name)} if parsed is not None else set()
276
+
277
+
278
+ def _known_decorator(
279
+ decorator: ast.expr,
280
+ function: ast.FunctionDef | ast.AsyncFunctionDef,
281
+ context: PythonFileContext,
282
+ semantics: AnnotationSemantics,
283
+ ) -> str | None:
284
+ if _has_wildcard_import(context):
285
+ return None
286
+ if _shadowed_annotation(decorator, function, context, semantics):
287
+ return None
288
+ if isinstance(decorator, ast.Name) and decorator.id in {"classmethod", "staticmethod"}:
289
+ if context.tree is not None and decorator.id in scope_bound_names(context.tree.body):
290
+ return None
291
+ if semantics.imports.builtin_is_unshadowed(decorator.id):
292
+ return "builtins." + decorator.id
293
+ return semantics.imports.resolved_qualified_name(decorator)
294
+
295
+
296
+ def _unknown_decorator(
297
+ function: ast.FunctionDef | ast.AsyncFunctionDef,
298
+ context: PythonFileContext,
299
+ semantics: AnnotationSemantics,
300
+ ) -> bool:
301
+ return any(
302
+ _known_decorator(decorator, function, context, semantics) not in _KNOWN_DECORATORS
303
+ for decorator in function.decorator_list
304
+ )
305
+
306
+
307
+ def _overload_implementations(
308
+ context: PythonFileContext,
309
+ semantics: AnnotationSemantics,
310
+ ) -> set[ast.FunctionDef | ast.AsyncFunctionDef]:
311
+ families: dict[tuple[ast.AST | None, str], list[ast.FunctionDef | ast.AsyncFunctionDef]] = {}
312
+ implementations: set[ast.FunctionDef | ast.AsyncFunctionDef] = set()
313
+ events = context.nodes(ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Name, ast.Import, ast.ImportFrom)
314
+ for node in sorted(events, key=lambda item: (item.lineno, item.col_offset)):
315
+ scope = _lexical_scope(node, context)
316
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
317
+ key = scope, node.name
318
+ if _signature_declaration(node, context, semantics):
319
+ families.setdefault(key, []).append(node)
320
+ else:
321
+ family = families.pop(key, [])
322
+ if _precise_family(family, semantics):
323
+ implementations.add(node)
324
+ else:
325
+ for name in _rebound_names(node):
326
+ families.pop((scope, name), None)
327
+ return implementations
328
+
329
+
330
+ def _lexical_scope(node: ast.AST, context: PythonFileContext) -> ast.AST | None:
331
+ parent = context.parents.get(node)
332
+ while parent is not None and not isinstance(
333
+ parent, (ast.Module, ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)
334
+ ):
335
+ parent = context.parents.get(parent)
336
+ return parent
337
+
338
+
339
+ def _precise_family(family: list[ast.FunctionDef | ast.AsyncFunctionDef], semantics: AnnotationSemantics) -> bool:
340
+ return len(family) >= _MIN_OVERLOAD_DECLARATIONS and all(
341
+ f.args.kwarg is None or not _is_broad(f.args.kwarg.annotation, semantics) for f in family
342
+ )
343
+
344
+
345
+ def _rebound_names(node: ast.AST) -> set[str]:
346
+ if isinstance(node, ast.Name) and isinstance(node.ctx, (ast.Store, ast.Del)):
347
+ return {node.id}
348
+ if isinstance(node, ast.ClassDef):
349
+ return {node.name}
350
+ if isinstance(node, (ast.Import, ast.ImportFrom)):
351
+ return scope_bound_names([node])
352
+ return set()
353
+
354
+
355
+ def _signature_declaration(
356
+ function: ast.FunctionDef | ast.AsyncFunctionDef,
357
+ context: PythonFileContext,
358
+ semantics: AnnotationSemantics,
359
+ ) -> bool:
360
+ decorators = {_known_decorator(decorator, function, context, semantics) for decorator in function.decorator_list}
361
+ return bool(decorators & _OVERLOAD_DECORATORS) or not decorators <= _KNOWN_DECORATORS