sarj-python-lint 1.69.1__tar.gz → 1.70.1__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 (251) hide show
  1. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_registry.py +2 -0
  4. sarj_python_lint-1.70.1/src/sarj_python_lint/rules/no_redundant_local_alias.py +397 -0
  5. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +4 -0
  6. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +4 -3
  7. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/.gitignore +0 -0
  8. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/LICENSE +0 -0
  9. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/README.md +0 -0
  10. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/__init__.py +0 -0
  11. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/__main__.py +0 -0
  12. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/_analysis_session.py +0 -0
  13. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/_file_context.py +0 -0
  14. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/_filesystem.py +0 -0
  15. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/_parallel.py +0 -0
  16. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/_python_target.py +0 -0
  17. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  18. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/_secret_names.py +0 -0
  19. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/_source.py +0 -0
  20. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/_version.py +0 -0
  21. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/benchmark.py +0 -0
  22. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/cast_access.py +0 -0
  23. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/interpreter_argv.py +0 -0
  24. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/json_boundary.py +0 -0
  25. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/py.typed +0 -0
  26. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/ratchet.py +0 -0
  27. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rule_base.py +0 -0
  28. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/__init__.py +0 -0
  29. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  30. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  31. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_ast_names.py +0 -0
  32. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  33. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  34. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_comments.py +0 -0
  35. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  36. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  37. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  38. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
  39. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  40. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  41. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_exports.py +0 -0
  42. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  43. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
  44. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  45. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_imports.py +0 -0
  46. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  47. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  48. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_local_source.py +0 -0
  49. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_logging.py +0 -0
  50. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  51. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  52. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  53. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  54. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_paths.py +0 -0
  55. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
  56. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  57. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  58. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
  59. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  60. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  61. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  62. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  63. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  64. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_sql.py +0 -0
  65. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  66. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_store_writes.py +0 -0
  67. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  68. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  69. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  70. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  71. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  72. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  73. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  74. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  75. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  76. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  77. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  78. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  79. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  80. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  81. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
  82. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  83. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  84. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  85. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  86. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  87. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  88. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  89. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  90. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  91. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  92. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  93. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  94. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  95. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  96. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  97. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  98. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  99. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  100. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  101. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  102. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  103. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  104. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  105. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  106. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  107. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  108. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  109. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  110. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  111. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  112. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  113. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  114. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  115. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  116. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  117. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  118. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  119. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  120. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  121. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  122. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  123. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  124. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  125. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  126. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  127. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  128. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  129. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  130. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  131. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  132. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  133. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  134. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  135. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  136. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  137. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  138. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  139. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  140. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  141. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  142. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  143. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  144. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  145. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  146. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  147. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  148. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  149. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  150. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  151. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  152. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  153. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  154. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  155. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  156. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  157. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  158. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  159. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  160. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  161. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  162. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  163. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  164. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  165. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  166. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  167. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  168. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  169. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  170. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  171. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  172. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  173. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  174. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  175. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  176. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
  177. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  178. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  179. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  180. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  181. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  182. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  183. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  184. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  185. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  186. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  187. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  188. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  189. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  190. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  191. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  192. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  193. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  194. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
  195. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  196. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  197. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
  198. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
  199. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  200. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  201. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  202. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  203. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  204. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  205. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  206. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  207. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
  208. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  209. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  210. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  211. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  212. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  213. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  214. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
  215. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  216. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  217. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  218. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  219. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  220. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  221. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  222. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  223. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  224. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  225. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  226. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  227. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  228. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  229. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  230. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  231. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  232. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  233. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  234. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
  235. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  236. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  237. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  238. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  239. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
  240. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  241. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  242. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  243. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  244. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  245. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  246. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  247. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  248. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  249. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  250. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
  251. {sarj_python_lint-1.69.1 → sarj_python_lint-1.70.1}/src/sarj_python_lint/suppression_policy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 1.69.1
3
+ Version: 1.70.1
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.69.1"
3
+ version = "1.70.1"
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" }]
@@ -232,6 +232,7 @@ from sarj_python_lint.rules.no_impossible_pydantic_field_bounds import NoImpossi
232
232
  from sarj_python_lint.rules.no_invalid_pydantic_bypass import NoInvalidPydanticBypass
233
233
  from sarj_python_lint.rules.no_invalid_pydantic_validator_return import NoInvalidPydanticValidatorReturn
234
234
  from sarj_python_lint.rules.no_partial_type_is import NoPartialTypeIs
235
+ from sarj_python_lint.rules.no_redundant_local_alias import NoRedundantLocalAlias
235
236
  from sarj_python_lint.rules.no_shallow_record_type_guard import NoShallowRecordTypeGuard
236
237
  from sarj_python_lint.rules.no_slash_collection_root_under_prefix import NoSlashCollectionRootUnderPrefix
237
238
  from sarj_python_lint.rules.no_unknown_pydantic_validator_field import NoUnknownPydanticValidatorField
@@ -254,6 +255,7 @@ from sarj_python_lint.rules.test_without_explicit_oracle import NoExplicitTestOr
254
255
 
255
256
  REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
256
257
  {
258
+ NoRedundantLocalAlias.id: NoRedundantLocalAlias,
257
259
  PytestFixtureReturnsClosedResource.id: PytestFixtureReturnsClosedResource,
258
260
  DuplicatePytestFixtureParams.id: DuplicatePytestFixtureParams,
259
261
  PreferTypedSettingsField.id: PreferTypedSettingsField,
@@ -0,0 +1,397 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from collections import defaultdict
5
+ from pathlib import PurePosixPath
6
+ import re
7
+ from typing import TYPE_CHECKING, ClassVar, final, override
8
+ import unicodedata
9
+
10
+ from sarj_python_lint.rule_base import (
11
+ AutofixPolicy,
12
+ Diagnostic,
13
+ ExampleFile,
14
+ ExampleOutcome,
15
+ Rule,
16
+ RuleCategory,
17
+ RuleDocumentation,
18
+ RuleExample,
19
+ Severity,
20
+ )
21
+ from sarj_python_lint.rules._ast_index import children
22
+ from sarj_python_lint.rules._suppression_comments import comment_lines
23
+
24
+
25
+ if TYPE_CHECKING:
26
+ from collections.abc import Mapping
27
+
28
+ from sarj_python_lint._file_context import PythonFileContext
29
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance
30
+
31
+
32
+ _FUNCTIONS = (ast.FunctionDef, ast.AsyncFunctionDef)
33
+ _SUITES = (*_FUNCTIONS, ast.With, ast.AsyncWith)
34
+ _UNCERTAIN = (
35
+ *_FUNCTIONS,
36
+ ast.ClassDef,
37
+ ast.Lambda,
38
+ ast.ListComp,
39
+ ast.SetComp,
40
+ ast.DictComp,
41
+ ast.GeneratorExp,
42
+ ast.If,
43
+ ast.For,
44
+ ast.AsyncFor,
45
+ ast.While,
46
+ ast.Try,
47
+ ast.TryStar,
48
+ ast.Match,
49
+ ast.Assert,
50
+ ast.BoolOp,
51
+ ast.Global,
52
+ ast.Nonlocal,
53
+ ast.TypeAlias,
54
+ )
55
+ _INITIALIZERS = (ast.Call, ast.Await, ast.Constant, ast.List, ast.Tuple, ast.Dict, ast.Set)
56
+ _REFERENCE_OBSERVERS = frozenset(
57
+ {
58
+ "getrefcount",
59
+ "get_referrers",
60
+ "getattr",
61
+ "attrgetter",
62
+ "TypeAdapter",
63
+ "BaseModel",
64
+ "RootModel",
65
+ "create_model",
66
+ "validate_call",
67
+ "model_rebuild",
68
+ "rebuild_dataclass",
69
+ "new_class",
70
+ }
71
+ )
72
+ _WORDS = re.compile(r"\w+")
73
+ _TYPE_COMMENT = re.compile(r"#\s*type\s*:")
74
+ _NAME_PAIR = re.compile(r"\b(?P<target>[A-Za-z_]\w*)[\s)\\]*=[\s(\\]*(?P<source>[A-Za-z_]\w*)\b(?=[\s),;\\]|$)")
75
+
76
+
77
+ @final
78
+ class NoRedundantLocalAlias(Rule):
79
+ id = "no-redundant-local-alias"
80
+ code = "SARJ531"
81
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
82
+ default_level=Severity.WARNING,
83
+ summary="Remove a local alias used only for its matching keyword argument.",
84
+ rationale="An extra local binding makes readers trace two names for one value when the keyword already communicates its role.",
85
+ remediation="Pass the original local directly to the matching keyword and remove the unannotated alias assignment after review.",
86
+ category=RuleCategory.MAINTAINABILITY,
87
+ autofix=AutofixPolicy.NONE,
88
+ limitations=(
89
+ "Only unannotated, single-target `alias = original` assignments with one binding and one read at `alias=alias` are checked.",
90
+ "The original must be a parameter or an earlier initialized local with one binding; reassignment, deletion, imports, alias chains and uncertain initialization are excluded.",
91
+ "Functions with if statements, loops, try statements, asserts, Boolean short-circuiting, nested scopes, global/nonlocal declarations or type aliases are excluded. Ordinary and async with bodies require ordered enclosing-suite binding evidence.",
92
+ "A conditional expression is admitted only when its test is a bare name bound once as a parameter, and neither local is read within the expression.",
93
+ "Typed aliases, files with type comments, same-scope quoted references and conditional reads of either local are excluded. Captured bindings and module/class aliases are preserved.",
94
+ "Files with wildcard imports, Pydantic imports or references to known namespace readers, dynamic attribute getters or reference-count observers are excluded. Wildcard exports cannot establish builtin identity; computed type names can consume caller-local bindings without a Name read in the AST.",
95
+ "Dynamic class construction and class-valued references to type are excluded. Direct, unshadowed unary type(value) queries and standard-library dataclasses remain supported.",
96
+ "Generated source, stubs and malformed input are excluded. Arbitrary external frame introspection is not modeled.",
97
+ "No autofix is offered; readability judgments, multiple reads, positional forwarding and more complex control flow remain outside the contract.",
98
+ ),
99
+ examples=(
100
+ RuleExample(
101
+ example_id="keyword-supplies-role",
102
+ title="The keyword supplies the role",
103
+ outcome=ExampleOutcome.MATCH,
104
+ files=(
105
+ ExampleFile.python(
106
+ "case.py", "def render(item):\n payload = item\n consume(payload=payload)\n"
107
+ ),
108
+ ),
109
+ focus_path=PurePosixPath("case.py"),
110
+ expected_count=1,
111
+ public=True,
112
+ ),
113
+ RuleExample(
114
+ example_id="direct-keyword",
115
+ title="Pass the original local",
116
+ outcome=ExampleOutcome.NO_MATCH,
117
+ files=(ExampleFile.python("case.py", "def render(item):\n consume(payload=item)\n"),),
118
+ focus_path=PurePosixPath("case.py"),
119
+ expected_count=0,
120
+ public=True,
121
+ ),
122
+ ),
123
+ )
124
+ description = documentation.summary
125
+
126
+ @override
127
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
128
+ if _excluded_file(context):
129
+ return []
130
+ keywords = _matching_keywords(context)
131
+ candidates = _local_assignments(context, set(keywords.values()))
132
+ if not candidates:
133
+ return []
134
+ provenance = context.resource_provenance
135
+ names = {node.targets[0].id for node in candidates if isinstance(node.targets[0], ast.Name)}
136
+ reads = _scope_reads(provenance, names)
137
+ conditional_sources = _conditional_sources(context)
138
+ quoted = _quoted_references(context, provenance, names)
139
+ positions = {statement: i for owner in context.nodes(*_SUITES) for i, statement in enumerate(owner.body)}
140
+ findings: list[Diagnostic] = []
141
+ for assignment in candidates:
142
+ target = assignment.targets[0]
143
+ source = assignment.value
144
+ if not isinstance(target, ast.Name) or not isinstance(source, ast.Name):
145
+ continue
146
+ if (provenance.scope(assignment), target.id) in quoted:
147
+ continue
148
+ use = _single_keyword_read(target, provenance, reads, keywords)
149
+ if use is None:
150
+ continue
151
+ if not _dominates(assignment, use, context.parents, positions):
152
+ continue
153
+ if not _stable_source(source, assignment, provenance, positions, conditional_sources):
154
+ continue
155
+ findings.append(
156
+ Diagnostic(
157
+ path=context.path,
158
+ line=target.lineno,
159
+ col=target.col_offset + 1,
160
+ code=self.code,
161
+ message=f"Local `{target.id}` only forwards `{source.id}` to `{target.id}=`; pass `{source.id}` directly and remove the alias.",
162
+ severity=Severity.WARNING,
163
+ )
164
+ )
165
+ return findings
166
+
167
+
168
+ def _excluded_file(context: PythonFileContext) -> bool:
169
+ if context.path.suffix != ".py" or "=" not in context.source:
170
+ return True
171
+ # This is only a necessary ASCII signal; Unicode identifiers use the AST.
172
+ if context.source.isascii() and not _possible_alias(context.source):
173
+ return True
174
+ return context.tree is None
175
+
176
+
177
+ def _possible_alias(source: str) -> bool:
178
+ # Comments may separate tokens; bypass the text gate instead of rescanning them.
179
+ if "#" in source:
180
+ return True
181
+ forwarded: set[str] = set()
182
+ assigned: set[str] = set()
183
+ for match in _NAME_PAIR.finditer(source):
184
+ target = match.group("target")
185
+ (forwarded if target == match.group("source") else assigned).add(target)
186
+ return not forwarded.isdisjoint(assigned)
187
+
188
+
189
+ def _matching_keywords(context: PythonFileContext) -> dict[ast.Name, str]:
190
+ return {
191
+ node.value: node.value.id
192
+ for node in context.nodes(ast.keyword)
193
+ if isinstance(node.value, ast.Name) and node.arg == node.value.id
194
+ }
195
+
196
+
197
+ def _local_assignments(context: PythonFileContext, names: set[str]) -> list[ast.Assign]:
198
+ candidates = [
199
+ node
200
+ for node in context.nodes(ast.Assign)
201
+ if len(node.targets) == 1
202
+ and isinstance(node.targets[0], ast.Name)
203
+ and node.targets[0].id in names
204
+ and isinstance(node.value, ast.Name)
205
+ and node.targets[0].id != node.value.id
206
+ ]
207
+ if not candidates or context.has_wildcard_import or context.dynamic_namespace or _observes_references(context):
208
+ return []
209
+ provenance = context.resource_provenance
210
+ blocked = {provenance.scope(node) for node in context.nodes(*_UNCERTAIN)}
211
+ blocked.update(
212
+ provenance.scope(node) for node in context.nodes(ast.IfExp) if not _parameter_choice(node, provenance)
213
+ )
214
+ eligible = [
215
+ node for node in candidates if isinstance(scope := provenance.scope(node), _FUNCTIONS) and scope not in blocked
216
+ ]
217
+ if not eligible or context.generated or _has_type_comments(context):
218
+ return []
219
+ return eligible
220
+
221
+
222
+ def _has_type_comments(context: PythonFileContext) -> bool:
223
+ return any(_TYPE_COMMENT.search(comment) for comment in comment_lines(tuple(context.source_lines)).values())
224
+
225
+
226
+ def _parameter_choice(node: ast.IfExp, provenance: ResourceProvenance) -> bool:
227
+ if not isinstance(node.test, ast.Name):
228
+ return False
229
+ bindings = provenance.bindings.get(provenance.scope(node), {}).get(node.test.id, ())
230
+ return len(bindings) == 1 and isinstance(bindings[0], ast.arg)
231
+
232
+
233
+ def _scope_reads(provenance: ResourceProvenance, names: set[str]) -> dict[tuple[ast.AST | None, str], list[ast.Name]]:
234
+ reads: dict[tuple[ast.AST | None, str], list[ast.Name]] = defaultdict(list)
235
+ for name in names:
236
+ for read in provenance.reads.get(name, ()):
237
+ reads[provenance.scope(read), name].append(read)
238
+ return reads
239
+
240
+
241
+ def _conditional_sources(context: PythonFileContext) -> set[tuple[ast.AST | None, str]]:
242
+ provenance = context.resource_provenance
243
+ pending: list[ast.AST] = list(context.nodes(ast.IfExp))
244
+ seen: set[ast.AST] = set()
245
+ names: set[tuple[ast.AST | None, str]] = set()
246
+ while pending:
247
+ node = pending.pop()
248
+ if node in seen:
249
+ continue
250
+ seen.add(node)
251
+ if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Load):
252
+ names.add((provenance.scope(node), node.id))
253
+ pending.extend(children(node))
254
+ return names
255
+
256
+
257
+ def _single_keyword_read(
258
+ target: ast.Name,
259
+ provenance: ResourceProvenance,
260
+ reads: Mapping[tuple[ast.AST | None, str], list[ast.Name]],
261
+ keywords: Mapping[ast.Name, str],
262
+ ) -> ast.Name | None:
263
+ scope = provenance.scope(target)
264
+ uses = reads.get((scope, target.id), ())
265
+ if provenance.bindings.get(scope, {}).get(target.id) != [target] or len(uses) != 1:
266
+ return None
267
+ [use] = uses
268
+ if keywords.get(use) != target.id or _statement(use, provenance.parents) is None:
269
+ return None
270
+ return use
271
+
272
+
273
+ def _quoted_references(
274
+ context: PythonFileContext,
275
+ provenance: ResourceProvenance,
276
+ names: set[str],
277
+ ) -> set[tuple[ast.AST | None, str]]:
278
+ references: set[tuple[ast.AST | None, str]] = set()
279
+ for node in context.nodes(ast.Constant):
280
+ if not isinstance(node.value, str):
281
+ continue
282
+ mentioned = _quoted_words(node.value) & names
283
+ if mentioned:
284
+ scope = provenance.scope(node)
285
+ references.update((scope, name) for name in mentioned)
286
+ return references
287
+
288
+
289
+ def _quoted_words(value: str) -> set[str]:
290
+ normalized = unicodedata.normalize("NFKC", value)
291
+ if normalized.isascii():
292
+ return set(_WORDS.findall(normalized))
293
+ words: set[str] = set()
294
+ word: list[str] = []
295
+ for character in normalized:
296
+ # A leading letter tests identifier continuation, including combining marks.
297
+ if ("a" + character).isidentifier():
298
+ word.append(character)
299
+ elif word:
300
+ words.add("".join(word))
301
+ word.clear()
302
+ if word:
303
+ words.add("".join(word))
304
+ return words
305
+
306
+
307
+ def _observes_references(context: PythonFileContext) -> bool:
308
+ if _imports_model_namespace(context):
309
+ return True
310
+ for node in context.nodes(ast.Name, ast.Attribute, ast.alias):
311
+ name = _reference_name(node)
312
+ if name in _REFERENCE_OBSERVERS or (name == "type" and not _plain_type_query(node, context)):
313
+ return True
314
+ return False
315
+
316
+
317
+ def _imports_model_namespace(context: PythonFileContext) -> bool:
318
+ for node in context.nodes(ast.Import, ast.ImportFrom):
319
+ if isinstance(node, ast.ImportFrom):
320
+ if node.level == 0 and (node.module or "").partition(".")[0] == "pydantic":
321
+ return True
322
+ elif any(alias.name.partition(".")[0] == "pydantic" for alias in node.names):
323
+ return True
324
+ return False
325
+
326
+
327
+ def _reference_name(node: ast.AST) -> str | None:
328
+ if isinstance(node, ast.Name):
329
+ return node.id
330
+ if isinstance(node, ast.Attribute):
331
+ return node.attr
332
+ return node.name if isinstance(node, ast.alias) else None
333
+
334
+
335
+ def _plain_type_query(node: ast.AST, context: PythonFileContext) -> bool:
336
+ parent = context.parents.get(node)
337
+ return (
338
+ isinstance(node, ast.Name)
339
+ and context.imports.builtin_is_unshadowed("type")
340
+ and isinstance(parent, ast.Call)
341
+ and parent.func is node
342
+ and len(parent.args) == 1
343
+ and not parent.keywords
344
+ and not isinstance(parent.args[0], ast.Starred)
345
+ )
346
+
347
+
348
+ def _statement(node: ast.AST, parents: Mapping[ast.AST, ast.AST]) -> ast.stmt | None:
349
+ while not isinstance(node, ast.stmt):
350
+ if isinstance(node, (ast.IfExp, ast.BoolOp)) or (parent := parents.get(node)) is None:
351
+ return None
352
+ node = parent
353
+ return node
354
+
355
+
356
+ def _dominates(
357
+ before: ast.stmt,
358
+ at: ast.AST,
359
+ parents: Mapping[ast.AST, ast.AST],
360
+ positions: Mapping[ast.stmt, int],
361
+ ) -> bool:
362
+ owner = parents.get(before)
363
+ if before not in positions or not isinstance(owner, _SUITES):
364
+ return False
365
+ current = at
366
+ while parents.get(current) is not owner:
367
+ parent = parents.get(current)
368
+ if parent is None:
369
+ return False
370
+ current = parent
371
+ # Exiting a with body can suppress an exception and skip its assignments.
372
+ # Only the same suite or an earlier enclosing-suite statement dominates.
373
+ return isinstance(current, ast.stmt) and current in positions and positions[before] < positions[current]
374
+
375
+
376
+ def _stable_source(
377
+ source: ast.Name,
378
+ at: ast.Assign,
379
+ provenance: ResourceProvenance,
380
+ positions: Mapping[ast.stmt, int],
381
+ conditional_sources: set[tuple[ast.AST | None, str]],
382
+ ) -> bool:
383
+ scope = provenance.scope(at)
384
+ if (scope, source.id) in conditional_sources:
385
+ return False
386
+ bindings = provenance.bindings.get(scope, {}).get(source.id, ())
387
+ if len(bindings) != 1:
388
+ return False
389
+ [binding] = bindings
390
+ if isinstance(binding, ast.arg):
391
+ return True
392
+ parent = provenance.parents.get(binding)
393
+ if not isinstance(binding, ast.Name) or not isinstance(parent, ast.Assign | ast.AnnAssign):
394
+ return False
395
+ if isinstance(parent, ast.Assign) and (len(parent.targets) != 1 or parent.targets[0] is not binding):
396
+ return False
397
+ return isinstance(parent.value, _INITIALIZERS) and _dominates(parent, at, provenance.parents, positions)
@@ -216,6 +216,10 @@ class NoUnnecessaryDocstring(ProjectRule):
216
216
  tree = context.tree
217
217
  if tree is None:
218
218
  return []
219
+ if not any(
220
+ docstring_expression(owner) is not None for owner in _docstring_owners(tree, node_index=context.node_index)
221
+ ):
222
+ return []
219
223
  metadata = _metadata_context_bindings(context, tree)
220
224
  owner_keys = _owner_keys(tree)
221
225
  explicit_consumers = _explicit_docstring_consumers(
@@ -175,11 +175,12 @@ class NoWholeRequestResponsePayloadInLog(Rule):
175
175
  tree = context.tree
176
176
  if tree is None:
177
177
  return []
178
+ calls = [node for node in context.nodes(ast.Call) if _is_logging_call(node)]
179
+ if not calls:
180
+ return []
178
181
  facts = _analysis_facts(context)
179
182
  diagnostics: list[Diagnostic] = []
180
- for node in context.nodes(ast.Call):
181
- if not _is_logging_call(node):
182
- continue
183
+ for node in calls:
183
184
  statement = _containing_statement(node, facts.parents)
184
185
  suite_id = facts.statement_suites.get(id(statement)) if statement is not None else None
185
186
  suite = facts.suites.get(suite_id) if suite_id is not None else None