sarj-python-lint 1.63.1__tar.gz → 1.66.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.66.0}/.gitignore +3 -0
  2. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/PKG-INFO +1 -1
  3. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/pyproject.toml +1 -1
  4. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/__main__.py +22 -25
  5. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_file_context.py +19 -0
  6. sarj_python_lint-1.66.0/src/sarj_python_lint/cast_access.py +252 -0
  7. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rule_base.py +3 -3
  8. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_annotation_semantics.py +4 -1
  9. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_copy_on_write.py +2 -4
  10. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +1 -0
  11. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +2 -2
  12. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_registry.py +2 -0
  13. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_resource_provenance.py +39 -28
  14. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_sql.py +25 -15
  15. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_store_writes.py +3 -3
  16. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +2 -2
  17. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +2 -2
  18. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +3 -3
  19. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +2 -2
  20. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +1 -1
  21. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +36 -30
  22. sarj_python_lint-1.66.0/src/sarj_python_lint/rules/no_blanket_suppression.py +134 -0
  23. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +3 -3
  24. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +3 -7
  25. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +1 -1
  26. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +2 -2
  27. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +2 -2
  28. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +2 -2
  29. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +1 -2
  30. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +32 -33
  31. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +2 -2
  32. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_select_star.py +73 -30
  33. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +2 -2
  34. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +1 -2
  35. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +2 -2
  36. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +42 -12
  37. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +2 -2
  38. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +1 -2
  39. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +2 -2
  40. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +2 -2
  41. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +1 -1
  42. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +2 -2
  43. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +3 -3
  44. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +2 -2
  45. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +2 -2
  46. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +1 -2
  47. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +1 -0
  48. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +3 -3
  49. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +1 -2
  50. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +1 -2
  51. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +4 -6
  52. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +2 -2
  53. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/unused_mock_setup.py +2 -2
  54. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +2 -2
  55. sarj_python_lint-1.66.0/src/sarj_python_lint/suppression_policy.py +5 -0
  56. sarj_python_lint-1.63.1/src/sarj_python_lint/cast_access.py +0 -86
  57. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/LICENSE +0 -0
  58. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/README.md +0 -0
  59. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/__init__.py +0 -0
  60. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  61. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_filesystem.py +0 -0
  62. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_python_target.py +0 -0
  63. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  64. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_secret_names.py +0 -0
  65. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_source.py +0 -0
  66. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_version.py +0 -0
  67. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/benchmark.py +0 -0
  68. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  69. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/json_boundary.py +0 -0
  70. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/py.typed +0 -0
  71. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/ratchet.py +0 -0
  72. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  73. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  74. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  75. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  76. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  77. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  78. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  79. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  80. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  81. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  82. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  83. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  84. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  85. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  86. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  87. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  88. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  89. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  90. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  91. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  92. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  93. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  94. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  95. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  96. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  97. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  98. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  99. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  100. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  101. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  102. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  103. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  104. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  105. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  106. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  107. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  108. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  109. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  110. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  111. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  112. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  113. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  114. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  115. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  116. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  117. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  118. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  119. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  120. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  121. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  122. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  123. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  124. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  125. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  126. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  127. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  128. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  129. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  130. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  131. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  132. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  133. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  134. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  135. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  136. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  137. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  138. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  139. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  140. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  141. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  142. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  143. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  144. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  145. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  146. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  147. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  148. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  149. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  150. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  151. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  152. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  153. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  154. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  155. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  156. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  157. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  158. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  159. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  160. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  161. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  162. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  163. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  164. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  165. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  166. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  167. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  168. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  169. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  170. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  171. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  172. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  173. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  174. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  175. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  176. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  177. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  178. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  179. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  180. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  181. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  182. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  183. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  184. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  185. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  186. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  187. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  188. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  189. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  190. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  191. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  192. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  193. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  194. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  195. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  196. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  197. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  198. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  199. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  200. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  201. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  202. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  203. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  204. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  205. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  206. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  207. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  208. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  209. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  210. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  211. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  212. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  213. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  214. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  215. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  216. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  217. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  218. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  219. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  220. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  221. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  222. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  223. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  224. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  225. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  226. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  227. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  228. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  229. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  230. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  231. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  232. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  233. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  234. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  235. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  236. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  237. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  238. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  239. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  240. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  241. {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
@@ -44,6 +44,9 @@ Thumbs.db
44
44
  !.claude/skills/test-value/
45
45
  .claude/skills/test-value/*
46
46
  !.claude/skills/test-value/SKILL.md
47
+ !.claude/skills/100-passes/
48
+ .claude/skills/100-passes/*
49
+ !.claude/skills/100-passes/SKILL.md
47
50
  apps/docs/.wrangler/
48
51
  apps/docs/.dev.vars*
49
52
  !apps/docs/.dev.vars.example
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 1.63.1
3
+ Version: 1.66.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.66.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)
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, final
6
6
 
7
7
  from sarj_python_lint._analysis_session import AnalysisSession
8
8
  from sarj_python_lint._source import symbol_prefilter_source
9
+ from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics
9
10
  from sarj_python_lint.rules._ast_index import NodeIndex
10
11
  from sarj_python_lint.rules._fastapi import FastapiIndex
11
12
  from sarj_python_lint.rules._imports import ImportIndex
@@ -18,6 +19,7 @@ if TYPE_CHECKING:
18
19
  from pathlib import Path
19
20
 
20
21
  from sarj_python_lint.rules._copy_on_write import CopyOnWriteAnalysis
22
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance
21
23
  from sarj_python_lint.rules._test_provenance import TestProvenance
22
24
 
23
25
 
@@ -91,6 +93,10 @@ class PythonFileContext:
91
93
  def module_imports(self) -> ImportIndex:
92
94
  return ImportIndex.from_tree(self._valid_tree(), module_scope_only=True)
93
95
 
96
+ @cached_property
97
+ def annotation_semantics(self) -> AnnotationSemantics:
98
+ return AnnotationSemantics.from_imports(self._valid_tree(), self.module_imports)
99
+
94
100
  @cached_property
95
101
  def dynamic_namespace(self) -> bool:
96
102
  return dynamic_namespace_access(self.nodes(ast.AST), self.imports, self.parents)
@@ -117,3 +123,16 @@ class PythonFileContext:
117
123
  from sarj_python_lint.rules._copy_on_write import CopyOnWriteAnalysis # ruff: ignore[import-outside-top-level] — share ownership analysis only when requested
118
124
 
119
125
  return CopyOnWriteAnalysis(self)
126
+
127
+ @cached_property
128
+ def resource_provenance(self) -> ResourceProvenance:
129
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance # ruff: ignore[import-outside-top-level] -- file-owned binding facts are lazy.
130
+
131
+ # The owner caches this analysis; borrowing prevents a reference cycle.
132
+ return ResourceProvenance(self, borrow_context=True)
133
+
134
+ @cached_property
135
+ def static_resource_provenance(self) -> ResourceProvenance:
136
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance # ruff: ignore[import-outside-top-level] -- keep static-namespace policy in a separate cached analysis.
137
+
138
+ return ResourceProvenance(self, require_static_namespace=True, borrow_context=True)
@@ -0,0 +1,252 @@
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
+
10
+
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Iterator
13
+ from pathlib import Path
14
+
15
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance
16
+
17
+
18
+ _TYPING = frozenset({"typing", "typing_extensions"})
19
+ _GETATTR_ARITIES = frozenset({2, 3})
20
+ _MAX_LITERAL_DEPTH = 12
21
+ _CAST_LENGTH = len("cast")
22
+ _COMPREHENSIONS = (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)
23
+
24
+
25
+ @dataclass(frozen=True, slots=True)
26
+ class CastAccess:
27
+ node: ast.Call | ast.Subscript | ast.ImportFrom
28
+ qualified_api: str
29
+ kind: Literal["wildcard", "reflection"]
30
+
31
+
32
+ def indirect_typing_cast_accesses(path: Path, source: str) -> tuple[CastAccess, ...]:
33
+ context = PythonFileContext(path, source)
34
+ if not any(marker in context.symbol_source for marker in ("*", "getattr", "__dict__", "vars", "__getattribute__")):
35
+ return ()
36
+ if context.tree is None:
37
+ return ()
38
+ accesses = [
39
+ CastAccess(node, f"{node.module}.cast", "wildcard")
40
+ for node in context.nodes(ast.ImportFrom)
41
+ if node.level == 0 and node.module in _TYPING and any(alias.name == "*" for alias in node.names)
42
+ ]
43
+ accesses.extend(_reflection_accesses(context))
44
+ return tuple(sorted(accesses, key=lambda access: (access.node.lineno, access.node.col_offset)))
45
+
46
+
47
+ def _reflection_accesses(context: PythonFileContext) -> list[CastAccess]:
48
+ provenance = context.resource_provenance
49
+ candidates = [
50
+ (node, receiver)
51
+ for node in context.nodes(ast.Call, ast.Subscript)
52
+ if (receiver := _cast_receiver(node, provenance)) is not None
53
+ ]
54
+ if not candidates:
55
+ return []
56
+ receivers = {id(receiver) for _, receiver in candidates}
57
+ proven = [
58
+ (node, module)
59
+ for node, receiver in candidates
60
+ if (module := _typing_receiver(receiver, provenance, receivers)) is not None
61
+ ]
62
+ safe_reflection = {id(carrier) for node, _ in proven if (carrier := _reflection_carrier(node)) is not None}
63
+ namespace_hazard = dynamic_namespace_access(
64
+ (node for node in context.nodes(ast.AST) if id(node) not in safe_reflection), context.imports, context.parents
65
+ )
66
+ if provenance.wildcard or namespace_hazard or not provenance.builtin_namespace_untouched:
67
+ return []
68
+ return [CastAccess(node, f"{module}.cast", "reflection") for node, module in proven]
69
+
70
+
71
+ def _cast_receiver(node: ast.Call | ast.Subscript, provenance: ResourceProvenance) -> ast.Name | None:
72
+ if isinstance(node, ast.Call):
73
+ return _call_receiver(node, provenance)
74
+ if not isinstance(node.ctx, ast.Load) or _static_text(node.slice) != "cast":
75
+ return None
76
+ namespace = node.value
77
+ if isinstance(namespace, ast.Attribute) and namespace.attr == "__dict__" and isinstance(namespace.value, ast.Name):
78
+ return namespace.value
79
+ if isinstance(namespace, ast.Call) and len(namespace.args) == 1 and _builtin_call(namespace, "vars", provenance):
80
+ return namespace.args[0] if isinstance(namespace.args[0], ast.Name) else None
81
+ return None
82
+
83
+
84
+ def _call_receiver(node: ast.Call, provenance: ResourceProvenance) -> ast.Name | None:
85
+ if (
86
+ len(node.args) in _GETATTR_ARITIES
87
+ and _builtin_call(node, "getattr", provenance)
88
+ and _static_text(node.args[1]) == "cast"
89
+ ):
90
+ return node.args[0] if isinstance(node.args[0], ast.Name) else None
91
+ if (
92
+ isinstance(node.func, ast.Attribute)
93
+ and node.func.attr == "__getattribute__"
94
+ and len(node.args) == 1
95
+ and not node.keywords
96
+ and _static_text(node.args[0]) == "cast"
97
+ ):
98
+ return node.func.value if isinstance(node.func.value, ast.Name) else None
99
+ return None
100
+
101
+
102
+ def _builtin_call(call: ast.Call, name: str, provenance: ResourceProvenance) -> bool:
103
+ if not isinstance(call.func, ast.Name) or call.func.id != name or call.keywords:
104
+ return False
105
+ return not _type_parameter_shadowed(name, call.func, provenance) and all(
106
+ name not in provenance.bindings.get(scope, {}) for scope in _lexical_scopes(call.func, provenance)
107
+ )
108
+
109
+
110
+ def _lexical_scopes(node: ast.AST, provenance: ResourceProvenance) -> Iterator[ast.AST]:
111
+ scope = _evaluation_scope(node, provenance)
112
+ immediate = True
113
+ while scope is not None:
114
+ # Ordinary nested code blocks do not capture class locals.
115
+ if immediate or not isinstance(scope, ast.ClassDef):
116
+ yield scope
117
+ immediate = False
118
+ scope = _evaluation_scope(scope, provenance)
119
+
120
+
121
+ def _evaluation_scope(node: ast.AST, provenance: ResourceProvenance) -> ast.AST | None:
122
+ scope = provenance.scope(node)
123
+ if isinstance(scope, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) and _owner_annotation(
124
+ node, scope, provenance
125
+ ):
126
+ node, scope = scope, provenance.scope(scope)
127
+ while isinstance(scope, _COMPREHENSIONS):
128
+ iterable = scope.generators[0].iter
129
+ if iterable is not node and not provenance.contains(iterable, node):
130
+ break
131
+ # A comprehension's first iterable is evaluated in the enclosing scope.
132
+ node, scope = scope, provenance.scope(scope)
133
+ return scope
134
+
135
+
136
+ def _owner_annotation(
137
+ node: ast.AST, owner: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef, provenance: ResourceProvenance
138
+ ) -> bool:
139
+ if any(parameter is node or provenance.contains(parameter, node) for parameter in owner.type_params):
140
+ return True
141
+ return isinstance(owner, ast.FunctionDef | ast.AsyncFunctionDef) and _function_annotation(node, owner, provenance)
142
+
143
+
144
+ def _function_annotation(
145
+ node: ast.AST, function: ast.FunctionDef | ast.AsyncFunctionDef, provenance: ResourceProvenance
146
+ ) -> bool:
147
+ current = node
148
+ while (parent := provenance.context.parents.get(current)) is not None:
149
+ if (parent is function and current is function.returns) or (
150
+ isinstance(parent, ast.arg) and current is parent.annotation
151
+ ):
152
+ return True
153
+ if parent is function:
154
+ break
155
+ current = parent
156
+ return False
157
+
158
+
159
+ def _type_parameter_shadowed(name: str, node: ast.AST, provenance: ResourceProvenance) -> bool:
160
+ current: ast.AST | None = node
161
+ while current is not None:
162
+ if (
163
+ isinstance(current, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.TypeAlias)
164
+ and any(
165
+ isinstance(parameter, ast.TypeVar | ast.TypeVarTuple | ast.ParamSpec) and parameter.name == name
166
+ for parameter in current.type_params
167
+ )
168
+ and not _outside_parameter_scope(node, current, provenance)
169
+ and not _local_binding_overrides(name, node, current, provenance)
170
+ ):
171
+ return True
172
+ current = provenance.context.parents.get(current)
173
+ return False
174
+
175
+
176
+ def _outside_parameter_scope(
177
+ node: ast.AST,
178
+ owner: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.TypeAlias,
179
+ provenance: ResourceProvenance,
180
+ ) -> bool:
181
+ outside = [] if isinstance(owner, ast.TypeAlias) else list(owner.decorator_list)
182
+ if isinstance(owner, ast.FunctionDef | ast.AsyncFunctionDef):
183
+ outside.extend(owner.args.defaults)
184
+ outside.extend(value for value in owner.args.kw_defaults if value is not None)
185
+ # Defaults and decorators evaluate in the enclosing scope.
186
+ return any(value is node or provenance.contains(value, node) for value in outside)
187
+
188
+
189
+ def _local_binding_overrides(name: str, node: ast.AST, owner: ast.AST, provenance: ResourceProvenance) -> bool:
190
+ for scope in _lexical_scopes(node, provenance):
191
+ if scope is not owner and not provenance.contains(owner, scope):
192
+ return False
193
+ if name in provenance.bindings.get(scope, {}):
194
+ return True
195
+ return False
196
+
197
+
198
+ def _static_text(node: ast.expr, depth: int = 0) -> str | None:
199
+ # Evaluate only bounded, side-effect-free string syntax, never runtime code.
200
+ if depth > _MAX_LITERAL_DEPTH:
201
+ return None
202
+ if isinstance(node, ast.Constant) and isinstance(node.value, str):
203
+ return node.value if len(node.value) <= _CAST_LENGTH else None
204
+ if isinstance(node, ast.JoinedStr):
205
+ # Parsing adjacent static f-strings folds them into one Constant.
206
+ return _static_text(node.values[0], depth + 1) if len(node.values) == 1 else None
207
+ if not isinstance(node, ast.BinOp) or not isinstance(node.op, ast.Add):
208
+ return None
209
+ left, right = _static_text(node.left, depth + 1), _static_text(node.right, depth + 1)
210
+ if left is None or right is None:
211
+ return None
212
+ text = left + right
213
+ return text if len(text) <= _CAST_LENGTH else None
214
+
215
+
216
+ def _reflection_carrier(node: ast.Call | ast.Subscript) -> ast.AST | None:
217
+ if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
218
+ return node.func
219
+ if isinstance(node, ast.Subscript):
220
+ return node.value.func if isinstance(node.value, ast.Call) else node.value
221
+ return None
222
+
223
+
224
+ def _typing_receiver(node: ast.Name, provenance: ResourceProvenance, receivers: set[int]) -> str | None:
225
+ if node.id in provenance.mutated | provenance.external_bindings:
226
+ return None
227
+ binding = _name_binding(node, provenance)
228
+ if not (
229
+ isinstance(binding, ast.alias)
230
+ and isinstance(provenance.context.parents.get(binding), ast.Import)
231
+ and binding.name in _TYPING
232
+ ):
233
+ return None
234
+ for read in provenance.reads.get(node.id, ()):
235
+ if _name_binding(read, provenance) is not binding:
236
+ continue
237
+ parent = provenance.context.parents.get(read)
238
+ if isinstance(parent, ast.Attribute) and parent.value is read:
239
+ continue
240
+ if id(read) in receivers:
241
+ continue
242
+ return None
243
+ return binding.name
244
+
245
+
246
+ def _name_binding(node: ast.Name, provenance: ResourceProvenance) -> ast.AST | None:
247
+ if _type_parameter_shadowed(node.id, node, provenance):
248
+ return None
249
+ for scope in _lexical_scopes(node, provenance):
250
+ if bindings := provenance.bindings.get(scope, {}).get(node.id):
251
+ return provenance.binding(node.id, bindings[0])
252
+ 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:
@@ -19,7 +19,10 @@ class AnnotationSemantics:
19
19
 
20
20
  @classmethod
21
21
  def from_tree(cls, tree: ast.Module, *, module_scope_only: bool = True) -> Self:
22
- imports = ImportIndex.from_tree(tree, module_scope_only=module_scope_only)
22
+ return cls.from_imports(tree, ImportIndex.from_tree(tree, module_scope_only=module_scope_only))
23
+
24
+ @classmethod
25
+ def from_imports(cls, tree: ast.Module, imports: ImportIndex) -> Self:
23
26
  candidates: dict[str, ast.expr] = {}
24
27
  ambiguous: set[str] = set()
25
28
  for statement in tree.body:
@@ -110,9 +110,7 @@ class _Models:
110
110
  def validation_owner(self, unit: SourceUnit) -> _ValidationOwner:
111
111
  if unit.path not in self._validation_owners:
112
112
  context = PythonFileContext(unit.path, unit.source, self._validation_session)
113
- self._validation_owners[unit.path] = _ValidationOwner(
114
- context, ResourceProvenance(context, require_static_namespace=True)
115
- )
113
+ self._validation_owners[unit.path] = _ValidationOwner(context, context.static_resource_provenance)
116
114
  return self._validation_owners[unit.path]
117
115
 
118
116
  def annotations(self, unit: SourceUnit) -> AnnotationSemantics:
@@ -378,7 +376,7 @@ class CopyOnWriteAnalysis:
378
376
 
379
377
  @cached_property
380
378
  def _validator_provider(self) -> ResourceProvenance:
381
- return ResourceProvenance(self.context, require_static_namespace=True)
379
+ return self.context.static_resource_provenance
382
380
 
383
381
  def _function(self, function: _Function) -> None:
384
382
  parameters = [*function.args.posonlyargs, *function.args.args, *function.args.kwonlyargs]
@@ -72,6 +72,7 @@ def data_union_diagnostics(context: PythonFileContext, code: str) -> list[Diagno
72
72
  class _DataUnions:
73
73
  def __init__(self, context: PythonFileContext, tree: ast.Module) -> None:
74
74
  self.context = context
75
+ # This analysis refines mutation ownership; keep its provenance private.
75
76
  self.provenance = ResourceProvenance(context, require_static_namespace=True)
76
77
  self.provenance.mutated = _scoped_module_mutations(self.provenance, tree)
77
78
  bindings = self.provenance.bindings.get(tree, {})
@@ -13,11 +13,11 @@ from sarj_python_lint.json_boundary import is_object_mapping
13
13
  from sarj_python_lint.rules._first_party import distribution_root
14
14
  from sarj_python_lint.rules._namespace_access import dynamic_namespace_access
15
15
  from sarj_python_lint.rules._native_namespace_capability import native_namespace_capability_escapes
16
- from sarj_python_lint.rules._resource_provenance import ResourceProvenance
17
16
 
18
17
 
19
18
  if TYPE_CHECKING:
20
19
  from sarj_python_lint._file_context import PythonFileContext
20
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance
21
21
 
22
22
 
23
23
  MODEL_SOURCES = frozenset({"pydantic", "pydantic.main"})
@@ -43,7 +43,7 @@ def direct_models(context: PythonFileContext, *, methods: bool = False) -> tuple
43
43
  or dynamic_namespace_access(context.nodes(ast.AST), context.imports, context.parents)
44
44
  ):
45
45
  return ()
46
- provenance = ResourceProvenance(context, require_static_namespace=True)
46
+ provenance = context.static_resource_provenance
47
47
  return tuple(
48
48
  node
49
49
  for node in tree.body
@@ -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,