sarj-python-lint 1.63.1__tar.gz → 1.64.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 (241) hide show
  1. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/__main__.py +22 -25
  4. sarj_python_lint-1.64.0/src/sarj_python_lint/cast_access.py +251 -0
  5. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rule_base.py +3 -3
  6. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_registry.py +2 -0
  7. sarj_python_lint-1.64.0/src/sarj_python_lint/rules/no_blanket_suppression.py +134 -0
  8. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +42 -12
  9. sarj_python_lint-1.64.0/src/sarj_python_lint/suppression_policy.py +5 -0
  10. sarj_python_lint-1.63.1/src/sarj_python_lint/cast_access.py +0 -86
  11. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/.gitignore +0 -0
  12. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/LICENSE +0 -0
  13. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/README.md +0 -0
  14. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/__init__.py +0 -0
  15. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  16. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/_file_context.py +0 -0
  17. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/_filesystem.py +0 -0
  18. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/_python_target.py +0 -0
  19. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  20. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/_secret_names.py +0 -0
  21. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/_source.py +0 -0
  22. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/_version.py +0 -0
  23. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/benchmark.py +0 -0
  24. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  25. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/json_boundary.py +0 -0
  26. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/py.typed +0 -0
  27. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/ratchet.py +0 -0
  28. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  29. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  30. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  31. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  32. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  33. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  34. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  35. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  36. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  37. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  38. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  39. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  40. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  41. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  42. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  43. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  44. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  45. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  46. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  47. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  48. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  49. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  50. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  51. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  52. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  53. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  54. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  55. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  56. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  57. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  58. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  59. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  60. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  61. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  62. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  63. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  64. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
  65. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  66. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  67. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  68. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  69. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  70. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  71. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  72. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  73. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  74. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  75. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  76. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  77. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  78. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  79. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  80. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  81. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  82. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  83. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  84. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  85. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  86. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  87. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  88. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  89. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  90. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  91. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  92. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  93. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  94. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  95. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  96. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  97. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  98. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  99. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  100. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  101. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  102. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  103. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  104. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  105. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  106. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  107. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  108. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  109. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  110. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  111. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  112. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  113. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  114. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  115. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  116. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  117. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  118. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  119. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  120. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  121. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  122. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  123. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  124. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  125. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  126. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  127. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  128. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  129. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  130. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  131. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  132. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  133. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  134. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  135. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  136. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  137. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  138. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  139. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  140. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  141. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  142. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  143. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  144. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  145. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  146. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  147. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  148. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  149. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  150. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  151. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  152. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  153. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  154. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  155. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  156. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  157. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  158. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  159. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  160. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  161. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  162. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  163. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  164. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  165. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  166. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  167. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  168. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  169. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  170. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  171. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  172. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  173. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  174. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  175. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  176. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  177. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  178. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  179. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  180. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  181. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  182. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  183. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  184. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  185. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  186. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  187. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  188. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  189. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  190. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  191. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  192. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  193. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  194. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  195. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  196. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  197. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  198. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  199. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  200. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  201. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  202. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  203. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  204. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  205. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  206. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  207. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  208. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  209. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  210. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  211. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  212. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  213. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  214. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  215. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  216. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  217. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  218. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  219. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  220. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  221. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  222. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  223. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  224. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  225. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
  226. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  227. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  228. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  229. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  230. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  231. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  232. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  233. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  234. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  235. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  236. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  237. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  238. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  239. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  240. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  241. {sarj_python_lint-1.63.1 → sarj_python_lint-1.64.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.63.1
3
+ Version: 1.64.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.63.1"
3
+ version = "1.64.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" }]
@@ -23,6 +23,7 @@ from sarj_python_lint.rule_base import Diagnostic, ProjectRule, Rule, Severity,
23
23
  from sarj_python_lint.rules import REGISTRY
24
24
  from sarj_python_lint.rules._paths import clear_path_caches
25
25
  from sarj_python_lint.rules._project_index import ProjectIndexSet
26
+ from sarj_python_lint.suppression_policy import PROTECTED_SUPPRESSION_CODES
26
27
 
27
28
 
28
29
  SKIP_DIR_NAMES = frozenset(
@@ -46,11 +47,17 @@ SKIP_DIR_NAMES = frozenset(
46
47
  }
47
48
  )
48
49
 
49
- # Skip files larger than this — they are almost always generated/vendored, not
50
- # hand-written source worth linting.
50
+ # Bound resource use, but never turn an unanalyzed selected file into success.
51
51
  _MAX_FILE_BYTES = 500_000
52
52
 
53
53
 
54
+ def _checked_size(path: Path) -> Path:
55
+ if path.stat().st_size > _MAX_FILE_BYTES:
56
+ msg = f"selected Python source exceeds {_MAX_FILE_BYTES} bytes: {path}; use an explicit path exclusion for generated source"
57
+ raise ValueError(msg)
58
+ return path
59
+
60
+
54
61
  def expand_paths(paths: list[Path]) -> list[Path]:
55
62
  out: list[Path] = []
56
63
  for p in paths:
@@ -58,11 +65,7 @@ def expand_paths(paths: list[Path]) -> list[Path]:
58
65
  msg = f"input does not exist: {p}"
59
66
  raise ValueError(msg)
60
67
  if p.is_file():
61
- try:
62
- if p.stat().st_size <= _MAX_FILE_BYTES:
63
- out.append(p)
64
- except OSError:
65
- pass
68
+ out.append(_checked_size(p))
66
69
  continue
67
70
  out.extend(_python_files(p))
68
71
  return out
@@ -78,11 +81,8 @@ def _python_files(p: Path) -> list[Path]:
78
81
  if not fnmatch(name, "*.py"):
79
82
  continue
80
83
  child = directory / name
81
- try:
82
- if child.is_file() and child.stat().st_size <= _MAX_FILE_BYTES:
83
- out.append(child)
84
- except OSError:
85
- continue
84
+ if child.is_file():
85
+ out.append(_checked_size(child))
86
86
  return sorted(out)
87
87
 
88
88
 
@@ -109,12 +109,7 @@ def _check(rule_ids: list[str], paths: list[Path]) -> list[Diagnostic]:
109
109
  rule.prepare_session(session)
110
110
  clear_path_caches()
111
111
  expanded = expand_paths(paths)
112
- loaded: dict[Path, str] = {}
113
- for path in expanded:
114
- try:
115
- loaded[path] = read_python_source(path)
116
- except OSError:
117
- continue
112
+ loaded = {path: read_python_source(path) for path in expanded}
118
113
  project_rules = [rule for rule in rules if isinstance(rule, ProjectRule)]
119
114
  if project_rules:
120
115
  indexes = ProjectIndexSet.build(expanded, loaded, facts=session.first_party)
@@ -131,14 +126,13 @@ def check_source(
131
126
  rules: list[Rule], p: Path, source: str, *, session: AnalysisSession | None = None
132
127
  ) -> list[Diagnostic]:
133
128
  context = PythonFileContext(p, source, session)
129
+ if rules and context.tree is None:
130
+ msg = f"selected Python source could not be parsed: {p}"
131
+ raise ValueError(msg)
134
132
  source_lines = context.source_lines
135
133
  raw = [diagnostic for rule in rules for diagnostic in rule.check_context(context)]
136
134
  return deduplicate_diagnostics(
137
- [
138
- diagnostic
139
- for diagnostic in raw
140
- if diagnostic.code == "SARJ419" or not is_suppressed(source_lines, diagnostic.line, diagnostic.code)
141
- ],
135
+ [diagnostic for diagnostic in raw if not is_suppressed(source_lines, diagnostic.line, diagnostic.code)],
142
136
  source=source,
143
137
  context=context,
144
138
  )
@@ -157,6 +151,9 @@ def analyze(
157
151
 
158
152
  _DIAGNOSTIC_PRECEDENCE = MappingProxyType(
159
153
  {
154
+ "SARJ054": frozenset({"SARJ419"}),
155
+ "SARJ448": frozenset({"SARJ419"}),
156
+ "SARJ523": frozenset({"SARJ419"}),
160
157
  "SARJ050": frozenset({"SARJ420"}),
161
158
  "SARJ084": frozenset({"SARJ050", "SARJ420"}),
162
159
  "SARJ085": frozenset({"SARJ420"}),
@@ -417,7 +414,7 @@ def _explain(wanted: str) -> int:
417
414
  def _baseline_counts(diags: list[Diagnostic]) -> dict[str, dict[str, int]]:
418
415
  counts: dict[str, dict[str, int]] = {}
419
416
  for d in diags:
420
- if d.severity is Severity.WARNING:
417
+ if d.severity is Severity.WARNING or d.code in PROTECTED_SUPPRESSION_CODES:
421
418
  continue
422
419
  key = _baseline_path(d.path)
423
420
  counts.setdefault(key, {})
@@ -467,7 +464,7 @@ def _apply_baseline(
467
464
  seen: Counter[tuple[str, str]] = Counter()
468
465
  out: list[Diagnostic] = []
469
466
  for d in diags:
470
- if d.severity is Severity.WARNING:
467
+ if d.severity is Severity.WARNING or d.code in PROTECTED_SUPPRESSION_CODES:
471
468
  out.append(d)
472
469
  continue
473
470
  path_key = _baseline_path(d.path, root=root)
@@ -0,0 +1,251 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING, Literal
6
+
7
+ from sarj_python_lint._file_context import PythonFileContext
8
+ from sarj_python_lint.rules._namespace_access import dynamic_namespace_access
9
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance
10
+
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Iterator
14
+ from pathlib import Path
15
+
16
+
17
+ _TYPING = frozenset({"typing", "typing_extensions"})
18
+ _GETATTR_ARITIES = frozenset({2, 3})
19
+ _MAX_LITERAL_DEPTH = 12
20
+ _CAST_LENGTH = len("cast")
21
+ _COMPREHENSIONS = (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)
22
+
23
+
24
+ @dataclass(frozen=True, slots=True)
25
+ class CastAccess:
26
+ node: ast.Call | ast.Subscript | ast.ImportFrom
27
+ qualified_api: str
28
+ kind: Literal["wildcard", "reflection"]
29
+
30
+
31
+ def indirect_typing_cast_accesses(path: Path, source: str) -> tuple[CastAccess, ...]:
32
+ context = PythonFileContext(path, source)
33
+ if not any(marker in context.symbol_source for marker in ("*", "getattr", "__dict__", "vars", "__getattribute__")):
34
+ return ()
35
+ if context.tree is None:
36
+ return ()
37
+ accesses = [
38
+ CastAccess(node, f"{node.module}.cast", "wildcard")
39
+ for node in context.nodes(ast.ImportFrom)
40
+ if node.level == 0 and node.module in _TYPING and any(alias.name == "*" for alias in node.names)
41
+ ]
42
+ accesses.extend(_reflection_accesses(context))
43
+ return tuple(sorted(accesses, key=lambda access: (access.node.lineno, access.node.col_offset)))
44
+
45
+
46
+ def _reflection_accesses(context: PythonFileContext) -> list[CastAccess]:
47
+ provenance = ResourceProvenance(context)
48
+ candidates = [
49
+ (node, receiver)
50
+ for node in context.nodes(ast.Call, ast.Subscript)
51
+ if (receiver := _cast_receiver(node, provenance)) is not None
52
+ ]
53
+ if not candidates:
54
+ return []
55
+ receivers = {id(receiver) for _, receiver in candidates}
56
+ proven = [
57
+ (node, module)
58
+ for node, receiver in candidates
59
+ if (module := _typing_receiver(receiver, provenance, receivers)) is not None
60
+ ]
61
+ safe_reflection = {id(carrier) for node, _ in proven if (carrier := _reflection_carrier(node)) is not None}
62
+ namespace_hazard = dynamic_namespace_access(
63
+ (node for node in context.nodes(ast.AST) if id(node) not in safe_reflection), context.imports, context.parents
64
+ )
65
+ if provenance.wildcard or namespace_hazard or not provenance.builtin_namespace_untouched:
66
+ return []
67
+ return [CastAccess(node, f"{module}.cast", "reflection") for node, module in proven]
68
+
69
+
70
+ def _cast_receiver(node: ast.Call | ast.Subscript, provenance: ResourceProvenance) -> ast.Name | None:
71
+ if isinstance(node, ast.Call):
72
+ return _call_receiver(node, provenance)
73
+ if not isinstance(node.ctx, ast.Load) or _static_text(node.slice) != "cast":
74
+ return None
75
+ namespace = node.value
76
+ if isinstance(namespace, ast.Attribute) and namespace.attr == "__dict__" and isinstance(namespace.value, ast.Name):
77
+ return namespace.value
78
+ if isinstance(namespace, ast.Call) and len(namespace.args) == 1 and _builtin_call(namespace, "vars", provenance):
79
+ return namespace.args[0] if isinstance(namespace.args[0], ast.Name) else None
80
+ return None
81
+
82
+
83
+ def _call_receiver(node: ast.Call, provenance: ResourceProvenance) -> ast.Name | None:
84
+ if (
85
+ len(node.args) in _GETATTR_ARITIES
86
+ and _builtin_call(node, "getattr", provenance)
87
+ and _static_text(node.args[1]) == "cast"
88
+ ):
89
+ return node.args[0] if isinstance(node.args[0], ast.Name) else None
90
+ if (
91
+ isinstance(node.func, ast.Attribute)
92
+ and node.func.attr == "__getattribute__"
93
+ and len(node.args) == 1
94
+ and not node.keywords
95
+ and _static_text(node.args[0]) == "cast"
96
+ ):
97
+ return node.func.value if isinstance(node.func.value, ast.Name) else None
98
+ return None
99
+
100
+
101
+ def _builtin_call(call: ast.Call, name: str, provenance: ResourceProvenance) -> bool:
102
+ if not isinstance(call.func, ast.Name) or call.func.id != name or call.keywords:
103
+ return False
104
+ return not _type_parameter_shadowed(name, call.func, provenance) and all(
105
+ name not in provenance.bindings.get(scope, {}) for scope in _lexical_scopes(call.func, provenance)
106
+ )
107
+
108
+
109
+ def _lexical_scopes(node: ast.AST, provenance: ResourceProvenance) -> Iterator[ast.AST]:
110
+ scope = _evaluation_scope(node, provenance)
111
+ immediate = True
112
+ while scope is not None:
113
+ # Ordinary nested code blocks do not capture class locals.
114
+ if immediate or not isinstance(scope, ast.ClassDef):
115
+ yield scope
116
+ immediate = False
117
+ scope = _evaluation_scope(scope, provenance)
118
+
119
+
120
+ def _evaluation_scope(node: ast.AST, provenance: ResourceProvenance) -> ast.AST | None:
121
+ scope = provenance.scope(node)
122
+ if isinstance(scope, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) and _owner_annotation(
123
+ node, scope, provenance
124
+ ):
125
+ node, scope = scope, provenance.scope(scope)
126
+ while isinstance(scope, _COMPREHENSIONS):
127
+ iterable = scope.generators[0].iter
128
+ if iterable is not node and not provenance.contains(iterable, node):
129
+ break
130
+ # A comprehension's first iterable is evaluated in the enclosing scope.
131
+ node, scope = scope, provenance.scope(scope)
132
+ return scope
133
+
134
+
135
+ def _owner_annotation(
136
+ node: ast.AST, owner: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef, provenance: ResourceProvenance
137
+ ) -> bool:
138
+ if any(parameter is node or provenance.contains(parameter, node) for parameter in owner.type_params):
139
+ return True
140
+ return isinstance(owner, ast.FunctionDef | ast.AsyncFunctionDef) and _function_annotation(node, owner, provenance)
141
+
142
+
143
+ def _function_annotation(
144
+ node: ast.AST, function: ast.FunctionDef | ast.AsyncFunctionDef, provenance: ResourceProvenance
145
+ ) -> bool:
146
+ current = node
147
+ while (parent := provenance.context.parents.get(current)) is not None:
148
+ if (parent is function and current is function.returns) or (
149
+ isinstance(parent, ast.arg) and current is parent.annotation
150
+ ):
151
+ return True
152
+ if parent is function:
153
+ break
154
+ current = parent
155
+ return False
156
+
157
+
158
+ def _type_parameter_shadowed(name: str, node: ast.AST, provenance: ResourceProvenance) -> bool:
159
+ current: ast.AST | None = node
160
+ while current is not None:
161
+ if (
162
+ isinstance(current, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.TypeAlias)
163
+ and any(
164
+ isinstance(parameter, ast.TypeVar | ast.TypeVarTuple | ast.ParamSpec) and parameter.name == name
165
+ for parameter in current.type_params
166
+ )
167
+ and not _outside_parameter_scope(node, current, provenance)
168
+ and not _local_binding_overrides(name, node, current, provenance)
169
+ ):
170
+ return True
171
+ current = provenance.context.parents.get(current)
172
+ return False
173
+
174
+
175
+ def _outside_parameter_scope(
176
+ node: ast.AST,
177
+ owner: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.TypeAlias,
178
+ provenance: ResourceProvenance,
179
+ ) -> bool:
180
+ outside = [] if isinstance(owner, ast.TypeAlias) else list(owner.decorator_list)
181
+ if isinstance(owner, ast.FunctionDef | ast.AsyncFunctionDef):
182
+ outside.extend(owner.args.defaults)
183
+ outside.extend(value for value in owner.args.kw_defaults if value is not None)
184
+ # Defaults and decorators evaluate in the enclosing scope.
185
+ return any(value is node or provenance.contains(value, node) for value in outside)
186
+
187
+
188
+ def _local_binding_overrides(name: str, node: ast.AST, owner: ast.AST, provenance: ResourceProvenance) -> bool:
189
+ for scope in _lexical_scopes(node, provenance):
190
+ if scope is not owner and not provenance.contains(owner, scope):
191
+ return False
192
+ if name in provenance.bindings.get(scope, {}):
193
+ return True
194
+ return False
195
+
196
+
197
+ def _static_text(node: ast.expr, depth: int = 0) -> str | None:
198
+ # Evaluate only bounded, side-effect-free string syntax, never runtime code.
199
+ if depth > _MAX_LITERAL_DEPTH:
200
+ return None
201
+ if isinstance(node, ast.Constant) and isinstance(node.value, str):
202
+ return node.value if len(node.value) <= _CAST_LENGTH else None
203
+ if isinstance(node, ast.JoinedStr):
204
+ # Parsing adjacent static f-strings folds them into one Constant.
205
+ return _static_text(node.values[0], depth + 1) if len(node.values) == 1 else None
206
+ if not isinstance(node, ast.BinOp) or not isinstance(node.op, ast.Add):
207
+ return None
208
+ left, right = _static_text(node.left, depth + 1), _static_text(node.right, depth + 1)
209
+ if left is None or right is None:
210
+ return None
211
+ text = left + right
212
+ return text if len(text) <= _CAST_LENGTH else None
213
+
214
+
215
+ def _reflection_carrier(node: ast.Call | ast.Subscript) -> ast.AST | None:
216
+ if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
217
+ return node.func
218
+ if isinstance(node, ast.Subscript):
219
+ return node.value.func if isinstance(node.value, ast.Call) else node.value
220
+ return None
221
+
222
+
223
+ def _typing_receiver(node: ast.Name, provenance: ResourceProvenance, receivers: set[int]) -> str | None:
224
+ if node.id in provenance.mutated | provenance.external_bindings:
225
+ return None
226
+ binding = _name_binding(node, provenance)
227
+ if not (
228
+ isinstance(binding, ast.alias)
229
+ and isinstance(provenance.context.parents.get(binding), ast.Import)
230
+ and binding.name in _TYPING
231
+ ):
232
+ return None
233
+ for read in provenance.reads.get(node.id, ()):
234
+ if _name_binding(read, provenance) is not binding:
235
+ continue
236
+ parent = provenance.context.parents.get(read)
237
+ if isinstance(parent, ast.Attribute) and parent.value is read:
238
+ continue
239
+ if id(read) in receivers:
240
+ continue
241
+ return None
242
+ return binding.name
243
+
244
+
245
+ def _name_binding(node: ast.Name, provenance: ResourceProvenance) -> ast.AST | None:
246
+ if _type_parameter_shadowed(node.id, node, provenance):
247
+ return None
248
+ for scope in _lexical_scopes(node, provenance):
249
+ if bindings := provenance.bindings.get(scope, {}).get(node.id):
250
+ return provenance.binding(node.id, bindings[0])
251
+ return None
@@ -20,6 +20,7 @@ from sarj_rule_contracts import (
20
20
  )
21
21
 
22
22
  from sarj_python_lint._python_target import PythonTargetFacts
23
+ from sarj_python_lint.suppression_policy import PROTECTED_SUPPRESSION_CODES
23
24
 
24
25
 
25
26
  if TYPE_CHECKING:
@@ -43,7 +44,7 @@ _SARJ_NOQA_RE = re.compile(
43
44
 
44
45
 
45
46
  def is_suppressed(source_lines: Sequence[str], line: int, code: str) -> bool:
46
- if line < 1 or line > len(source_lines):
47
+ if code.upper() in PROTECTED_SUPPRESSION_CODES or line < 1 or line > len(source_lines):
47
48
  return False
48
49
  text = source_lines[line - 1]
49
50
  m = _SARJ_NOQA_RE.search(text)
@@ -58,8 +59,7 @@ def is_suppressed(source_lines: Sequence[str], line: int, code: str) -> bool:
58
59
  codes_str = m.group(1)
59
60
  trailing = comment[m.end() :]
60
61
  if codes_str is None:
61
- # A complete bare marker preserves the existing all-codes waiver contract.
62
- return not (trailing and (not trailing[0].isspace() or trailing.lstrip().startswith(":")))
62
+ return False
63
63
  reason = trailing.lstrip()
64
64
  single_dash_reason = reason.startswith("-") and (codes_str[-1:].isspace() or trailing[:1].isspace())
65
65
  if reason and not reason.startswith(("--", "—", "–")) and not single_dash_reason:
@@ -34,6 +34,7 @@ from sarj_python_lint.rules.no_analytical_aggregation_in_postgres_store import (
34
34
  from sarj_python_lint.rules.no_any_mapping_types import NoAnyMappingTypes
35
35
  from sarj_python_lint.rules.no_async_function_cache import NoAsyncFunctionCache
36
36
  from sarj_python_lint.rules.no_before_validator_input_mutation import NoBeforeValidatorInputMutation
37
+ from sarj_python_lint.rules.no_blanket_suppression import NoBlanketSuppression
37
38
  from sarj_python_lint.rules.no_broad_keyword_capture import NoBroadKeywordCapture
38
39
  from sarj_python_lint.rules.no_comment_cruft import NoCommentCruft
39
40
  from sarj_python_lint.rules.no_conftest_test_module_import import NoConftestTestModuleImport
@@ -329,6 +330,7 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
329
330
  RequirePydanticForExternalJson.id: RequirePydanticForExternalJson,
330
331
  RequirePydanticForMappingShape.id: RequirePydanticForMappingShape,
331
332
  NoDualShapeModelFieldReader.id: NoDualShapeModelFieldReader,
333
+ NoBlanketSuppression.id: NoBlanketSuppression,
332
334
  PreferModuleLevelConstant.id: PreferModuleLevelConstant,
333
335
  PreferImmutableModuleConstant.id: PreferImmutableModuleConstant,
334
336
  PreferInjectedDependencyOverMonkeypatch.id: PreferInjectedDependencyOverMonkeypatch,
@@ -0,0 +1,134 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import PurePosixPath
4
+ import re
5
+ from typing import TYPE_CHECKING, ClassVar, final, override
6
+
7
+ from sarj_python_lint.rule_base import (
8
+ AutofixPolicy,
9
+ ColumnEncoding,
10
+ Diagnostic,
11
+ ExampleFile,
12
+ ExampleOutcome,
13
+ Rule,
14
+ RuleCategory,
15
+ RuleDocumentation,
16
+ RuleExample,
17
+ )
18
+ from sarj_python_lint.rules._suppression_comments import scan_comments_or_none
19
+
20
+
21
+ if TYPE_CHECKING:
22
+ from sarj_python_lint._file_context import PythonFileContext
23
+
24
+
25
+ _SARJ = re.compile(r"(?:^|#\s*)sarj-noqa\b(?P<tail>[^#]*)", re.IGNORECASE)
26
+ _SARJ_CODES = re.compile(r"^:\s*SARJ\d+\b", re.IGNORECASE)
27
+ _PYRIGHT = re.compile(r"(?:^|#\s*)pyright:\s*(?P<tail>[^#]*)")
28
+ _SCOPED_IGNORE = re.compile(r"^ignore\s*\[\s*report[A-Za-z]+(?:\s*,\s*report[A-Za-z]+)*\s*,?\s*\]")
29
+ _DIAGNOSTIC = re.compile(r"^report[A-Z][A-Za-z]+$")
30
+ _WEAKENED = frozenset({"false", "none", "warning", "information", "hint"})
31
+ _WEAKER_MODES = frozenset({"basic", "standard"})
32
+ _DISABLED_CHECKS = frozenset({"analyzeUnannotatedFunctions", "disableBytesTypePromotions", "strictParameterNoneValue"})
33
+ _RUFF_BROAD = re.compile(
34
+ r"^ruff:\s*(?:file-ignore\s*\[(?P<file>[^\]]+)\]|noqa\s*:\s*(?P<codes>[A-Za-z0-9_, ]+)|disable\s*\[[^\]]+\])",
35
+ re.IGNORECASE,
36
+ )
37
+ _FLAKE8_BROAD = re.compile(r"^flake8:\s*noqa\s*:\s*[A-Za-z][A-Za-z0-9]*", re.IGNORECASE)
38
+ _BAD = "def value() -> str:\n return 1 # pyright: ignore\n"
39
+ _GOOD = "value = vendor.value # pyright: ignore[reportPrivateUsage] -- the dependency exposes this API only privately\n"
40
+
41
+
42
+ @final
43
+ class NoBlanketSuppression(Rule):
44
+ id = "no-blanket-suppression"
45
+ code = "SARJ523"
46
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
47
+ summary="Use named, line-local suppressions instead of blanket, file or range disables.",
48
+ rationale=(
49
+ "A blanket suppression hides unrelated errors on its line. A file-level diagnostic downgrade also "
50
+ "authorizes future defects outside the reviewed exception. Suppression checks cannot waive themselves."
51
+ ),
52
+ remediation="Fix the diagnostic or use an exact, line-local rule code with a concrete boundary reason.",
53
+ category=RuleCategory.CORRECTNESS,
54
+ autofix=AutofixPolicy.NONE,
55
+ limitations=(
56
+ "Checks actual Python comment tokens for bare or empty sarj-noqa, unscoped pyright: ignore, basic/standard Pyright modes, report* downgrades below error, analyzeUnannotatedFunctions/disableBytesTypePromotions/strictParameterNoneValue=false and scoped Ruff/Flake8 file or Ruff range disables. SARJ054 retains precedence for Ruff banned-api file directives.",
57
+ "Ruff owns blanket noqa and type: ignore through PGH004 and PGH003. Exact local exceptions remain available; this rule does not judge whether their explanations justify the exception.",
58
+ "Directive examples inside strings and unrelated Pyright feature flags are excluded. Source-generated markers cannot waive accountability; use explicit whole-path exclusions for generated/vendor code. Consumer type-checker configuration remains outside this source rule.",
59
+ "The full Standards gate protects this rule and the existing suppression-accountability checks from inline waivers, rule exclusions and diagnostic baselines. Explicitly selected partial checks remain scoped.",
60
+ ),
61
+ examples=(
62
+ RuleExample(
63
+ example_id="blanket-type-ignore",
64
+ title="An unscoped ignore hides every diagnostic on the line",
65
+ outcome=ExampleOutcome.MATCH,
66
+ files=(ExampleFile.python("app/adapter.py", _BAD),),
67
+ focus_path=PurePosixPath("app/adapter.py"),
68
+ expected_count=1,
69
+ public=True,
70
+ ),
71
+ RuleExample(
72
+ example_id="exact-local-exception",
73
+ title="A named diagnostic retains the dependency boundary explanation",
74
+ outcome=ExampleOutcome.NO_MATCH,
75
+ files=(ExampleFile.python("app/adapter.py", _GOOD),),
76
+ focus_path=PurePosixPath("app/adapter.py"),
77
+ expected_count=0,
78
+ public=True,
79
+ ),
80
+ ),
81
+ )
82
+ description = documentation.summary
83
+
84
+ @override
85
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
86
+ findings: list[Diagnostic] = []
87
+ for comment in scan_comments_or_none(context.source) or ():
88
+ bare_sarj = any(_SARJ_CODES.match(match["tail"]) is None for match in _SARJ.finditer(comment.body))
89
+ broad_pyright = any(
90
+ _broad_pyright(match["tail"].strip(), configuration=match.start() == 0)
91
+ for match in _PYRIGHT.finditer(comment.body)
92
+ )
93
+ broad_ruff = _broad_ruff(comment.body) if comment.standalone else False
94
+ if bare_sarj or broad_pyright or broad_ruff:
95
+ findings.append(
96
+ Diagnostic(
97
+ context.path,
98
+ comment.line,
99
+ comment.col,
100
+ self.code,
101
+ "Use a named, line-local suppression with a concrete reason; blanket ignores and "
102
+ "file/range disables hide unrelated diagnostics.",
103
+ column_encoding=ColumnEncoding.CODEPOINTS,
104
+ )
105
+ )
106
+ return findings
107
+
108
+
109
+ def _broad_ruff(body: str) -> bool:
110
+ if _FLAKE8_BROAD.match(body) is not None:
111
+ return True
112
+ match = _RUFF_BROAD.match(body)
113
+ if match is None:
114
+ return False
115
+ selectors = (match["file"] or match["codes"] or "").upper()
116
+ return not {"TID251", "BANNED-API"}.intersection(part.strip() for part in selectors.split(","))
117
+
118
+
119
+ def _broad_pyright(tail: str, *, configuration: bool) -> bool:
120
+ if re.match(r"ignore(?=$|\s|\[)", tail) is not None:
121
+ return _SCOPED_IGNORE.match(tail) is None
122
+ if not configuration:
123
+ return False
124
+ options = tail.split("--", 1)[0].split("—", 1)[0].split(",")
125
+ for raw in options:
126
+ option = raw.strip()
127
+ if option in _WEAKER_MODES:
128
+ return True
129
+ name, separator, value = option.partition("=")
130
+ if separator and name.strip() in _DISABLED_CHECKS and value.strip() == "false":
131
+ return True
132
+ if separator and _DIAGNOSTIC.fullmatch(name.strip()) and value.strip() in _WEAKENED:
133
+ return True
134
+ return False