sarj-python-lint 1.64.0__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 (240) hide show
  1. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/.gitignore +3 -0
  2. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/PKG-INFO +1 -1
  3. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/pyproject.toml +1 -1
  4. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_file_context.py +19 -0
  5. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/cast_access.py +3 -2
  6. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_annotation_semantics.py +4 -1
  7. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_copy_on_write.py +2 -4
  8. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +1 -0
  9. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +2 -2
  10. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_resource_provenance.py +39 -28
  11. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_sql.py +25 -15
  12. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_store_writes.py +3 -3
  13. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +2 -2
  14. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +2 -2
  15. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +3 -3
  16. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +2 -2
  17. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +1 -1
  18. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +36 -30
  19. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +3 -3
  20. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +3 -7
  21. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +1 -1
  22. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +2 -2
  23. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +2 -2
  24. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +2 -2
  25. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +1 -2
  26. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +32 -33
  27. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +2 -2
  28. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_select_star.py +73 -30
  29. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +2 -2
  30. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +1 -2
  31. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +2 -2
  32. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +2 -2
  33. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +1 -2
  34. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +2 -2
  35. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +2 -2
  36. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +1 -1
  37. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +2 -2
  38. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +3 -3
  39. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +2 -2
  40. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +2 -2
  41. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +1 -2
  42. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +1 -0
  43. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +3 -3
  44. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +1 -2
  45. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +1 -2
  46. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +4 -6
  47. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +2 -2
  48. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/unused_mock_setup.py +2 -2
  49. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +2 -2
  50. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/LICENSE +0 -0
  51. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/README.md +0 -0
  52. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/__init__.py +0 -0
  53. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/__main__.py +0 -0
  54. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  55. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_filesystem.py +0 -0
  56. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_python_target.py +0 -0
  57. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  58. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_secret_names.py +0 -0
  59. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_source.py +0 -0
  60. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_version.py +0 -0
  61. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/benchmark.py +0 -0
  62. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  63. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/json_boundary.py +0 -0
  64. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/py.typed +0 -0
  65. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/ratchet.py +0 -0
  66. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rule_base.py +0 -0
  67. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  68. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  69. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  70. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  71. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  72. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  73. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  74. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  75. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  76. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  77. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  78. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  79. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  80. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  81. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  82. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  83. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  84. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  85. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  86. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  87. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  88. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  89. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  90. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  91. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  92. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_registry.py +0 -0
  93. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  94. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  95. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  96. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  97. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  98. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  99. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  100. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  101. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  102. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  103. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  104. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  105. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  106. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  107. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  108. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  109. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  110. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  111. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  112. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  113. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  114. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  115. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  116. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  117. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  118. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  119. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  120. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  121. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  122. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  123. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  124. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  125. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  126. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  127. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  128. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  129. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  130. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  131. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  132. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  133. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  134. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  135. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  136. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  137. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  138. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  139. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  140. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  141. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  142. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  143. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  144. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  145. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  146. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  147. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  148. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  149. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  150. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  151. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  152. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  153. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  154. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  155. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  156. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  157. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  158. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  159. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  160. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  161. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  162. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  163. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  164. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  165. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  166. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  167. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  168. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  169. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  170. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  171. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  172. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  173. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  174. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  175. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  176. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  177. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  178. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  179. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  180. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  181. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  182. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  183. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  184. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  185. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  186. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  187. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  188. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  189. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  190. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  191. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  192. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  193. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  194. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  195. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  196. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  197. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  198. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  199. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  200. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  201. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  202. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  203. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  204. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  205. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  206. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  207. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  208. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  209. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  210. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  211. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  212. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  213. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  214. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  215. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  216. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  217. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  218. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  219. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  220. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  221. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  222. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  223. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  224. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  225. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  226. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  227. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  228. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  229. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  230. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  231. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  232. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  233. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  234. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  235. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  236. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  237. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  238. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  239. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  240. {sarj_python_lint-1.64.0 → sarj_python_lint-1.66.0}/src/sarj_python_lint/suppression_policy.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.64.0
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.64.0"
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" }]
@@ -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)
@@ -6,13 +6,14 @@ from typing import TYPE_CHECKING, Literal
6
6
 
7
7
  from sarj_python_lint._file_context import PythonFileContext
8
8
  from sarj_python_lint.rules._namespace_access import dynamic_namespace_access
9
- from sarj_python_lint.rules._resource_provenance import ResourceProvenance
10
9
 
11
10
 
12
11
  if TYPE_CHECKING:
13
12
  from collections.abc import Iterator
14
13
  from pathlib import Path
15
14
 
15
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance
16
+
16
17
 
17
18
  _TYPING = frozenset({"typing", "typing_extensions"})
18
19
  _GETATTR_ARITIES = frozenset({2, 3})
@@ -44,7 +45,7 @@ def indirect_typing_cast_accesses(path: Path, source: str) -> tuple[CastAccess,
44
45
 
45
46
 
46
47
  def _reflection_accesses(context: PythonFileContext) -> list[CastAccess]:
47
- provenance = ResourceProvenance(context)
48
+ provenance = context.resource_provenance
48
49
  candidates = [
49
50
  (node, receiver)
50
51
  for node in context.nodes(ast.Call, ast.Subscript)
@@ -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
@@ -5,9 +5,9 @@ from collections import defaultdict
5
5
  from functools import cached_property
6
6
  from types import MappingProxyType
7
7
  from typing import TYPE_CHECKING, final
8
+ from weakref import ReferenceType, ref
8
9
 
9
10
  from sarj_python_lint.rules._ast_index import has_own_yield
10
- from sarj_python_lint.rules._namespace_access import dynamic_namespace_access
11
11
  from sarj_python_lint.rules._runtime_bindings import RuntimeBindings
12
12
 
13
13
 
@@ -71,8 +71,13 @@ def builtin_namespace_untouched(context: PythonFileContext) -> bool:
71
71
 
72
72
  @final
73
73
  class ResourceProvenance:
74
- def __init__(self, context: PythonFileContext, *, require_static_namespace: bool = False) -> None:
75
- self.context = context
74
+ def __init__(
75
+ self, context: PythonFileContext, *, require_static_namespace: bool = False, borrow_context: bool = False
76
+ ) -> None:
77
+ self._context: PythonFileContext | ReferenceType[PythonFileContext] = (
78
+ ref(context) if borrow_context else context
79
+ )
80
+ self.parents = context.parents
76
81
  self.require_static_namespace = require_static_namespace
77
82
  self.bindings: dict[ast.AST | None, dict[str, list[ast.AST]]] = defaultdict(lambda: defaultdict(list))
78
83
  self.reads: dict[str, list[ast.Name]] = defaultdict(list)
@@ -98,15 +103,23 @@ class ResourceProvenance:
98
103
  if root is not None:
99
104
  self.mutated.add(root)
100
105
 
106
+ @property
107
+ def context(self) -> PythonFileContext:
108
+ context = self._context() if isinstance(self._context, ReferenceType) else self._context
109
+ if context is None:
110
+ msg = "The provenance file context has been released"
111
+ raise ReferenceError(msg)
112
+ return context
113
+
101
114
  @cached_property
102
115
  def builtin_namespace_untouched(self) -> bool:
103
116
  return builtin_namespace_untouched(self.context)
104
117
 
105
118
  def scope(self, node: ast.AST) -> ast.AST | None:
106
- parent = self.context.parents.get(node)
119
+ parent = self.parents.get(node)
107
120
  while parent is not None:
108
121
  if isinstance(parent, ast.arguments) and (node in parent.defaults or node in parent.kw_defaults):
109
- parent = self.context.parents.get(parent)
122
+ parent = self.parents.get(parent)
110
123
  elif isinstance(parent, _SCOPES) and not (
111
124
  (
112
125
  isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef)
@@ -118,12 +131,12 @@ class ResourceProvenance:
118
131
  if parent is None:
119
132
  return None
120
133
  node = parent
121
- parent = self.context.parents.get(parent)
134
+ parent = self.parents.get(parent)
122
135
  return None
123
136
 
124
137
  def _binding_scope(self, node: ast.AST) -> ast.AST | None:
125
138
  scope = self.scope(node)
126
- parent = self.context.parents.get(node)
139
+ parent = self.parents.get(node)
127
140
  if isinstance(parent, ast.NamedExpr) and parent.target is node:
128
141
  while isinstance(scope, ast.ListComp | ast.SetComp | ast.DictComp | ast.GeneratorExp):
129
142
  scope = self.scope(scope)
@@ -155,7 +168,7 @@ class ResourceProvenance:
155
168
  binding = self.runtime_binding(root, node)
156
169
  if not isinstance(binding, ast.alias):
157
170
  return None
158
- qualified = import_name_for_binding(node, binding, self.context.parents.get(binding))
171
+ qualified = import_name_for_binding(node, binding, self.parents.get(binding))
159
172
  if qualified is None or not self._owner_namespace_untouched(qualified.partition(".")[0]):
160
173
  return None
161
174
  if binding not in self.runtime_import_cache:
@@ -195,7 +208,7 @@ class ResourceProvenance:
195
208
  def _namespace_aliases(self) -> dict[str, tuple[ast.alias, ...]]:
196
209
  aliases: dict[str, list[ast.alias]] = defaultdict(list)
197
210
  for binding in self.context.nodes(ast.alias):
198
- parent = self.context.parents.get(binding)
211
+ parent = self.parents.get(binding)
199
212
  name = binding.asname or (
200
213
  binding.name.partition(".")[0] if isinstance(parent, ast.Import) else binding.name
201
214
  )
@@ -231,7 +244,7 @@ class ResourceProvenance:
231
244
  return paths
232
245
 
233
246
  def _namespace_module_path(self, binding: ast.alias, attributes: tuple[str, ...]) -> Path | None:
234
- parent = self.context.parents.get(binding)
247
+ parent = self.parents.get(binding)
235
248
  match parent:
236
249
  case ast.Import():
237
250
  prefix = binding.name if binding.asname else binding.name.partition(".")[0]
@@ -250,7 +263,7 @@ class ResourceProvenance:
250
263
  for node in nodes:
251
264
  if not isinstance(node, ast.alias):
252
265
  return False
253
- parent = self.context.parents.get(node)
266
+ parent = self.parents.get(node)
254
267
  if isinstance(parent, ast.Import):
255
268
  targets.add(("import", None, 0, node.name))
256
269
  elif isinstance(parent, ast.ImportFrom):
@@ -271,7 +284,7 @@ class ResourceProvenance:
271
284
  return False
272
285
  binding = self.binding(root, node)
273
286
  for read in self.reads.get(root, ()):
274
- parent = self.context.parents.get(read)
287
+ parent = self.parents.get(read)
275
288
  if isinstance(parent, ast.Attribute | ast.ExceptHandler):
276
289
  continue
277
290
  if not self._safe_import_use(read):
@@ -317,7 +330,7 @@ class ResourceProvenance:
317
330
  )
318
331
 
319
332
  def _safe_import_use(self, node: ast.Name | ast.Attribute) -> bool:
320
- parent = self.context.parents.get(node)
333
+ parent = self.parents.get(node)
321
334
  return (
322
335
  (isinstance(parent, ast.Attribute) and parent.value is node)
323
336
  or (isinstance(parent, ast.Call) and parent.func is node)
@@ -337,9 +350,9 @@ class ResourceProvenance:
337
350
  binding = self.binding(name.id, at)
338
351
  if not isinstance(binding, ast.Name):
339
352
  return None
340
- statement = self.context.parents.get(binding)
353
+ statement = self.parents.get(binding)
341
354
  if isinstance(statement, ast.withitem):
342
- owner = self.context.parents.get(statement)
355
+ owner = self.parents.get(statement)
343
356
  if (
344
357
  binding is statement.optional_vars
345
358
  and self.contains(owner, at)
@@ -351,12 +364,12 @@ class ResourceProvenance:
351
364
  return value if isinstance(value, ast.Call) else None
352
365
 
353
366
  def assignment_value(self, binding: ast.Name, at: ast.AST) -> ast.expr | None:
354
- statement = self.context.parents.get(binding)
367
+ statement = self.parents.get(binding)
355
368
  if not isinstance(statement, ast.Assign | ast.AnnAssign):
356
369
  return None
357
370
  if isinstance(statement, ast.Assign) and len(statement.targets) != 1:
358
371
  return None
359
- if not isinstance(self.context.parents.get(statement), ast.Module | ast.FunctionDef | ast.AsyncFunctionDef):
372
+ if not isinstance(self.parents.get(statement), ast.Module | ast.FunctionDef | ast.AsyncFunctionDef):
360
373
  return None
361
374
  return statement.value if statement.lineno < getattr(at, "lineno", 0) else None
362
375
 
@@ -381,12 +394,10 @@ class ResourceProvenance:
381
394
 
382
395
  @cached_property
383
396
  def dynamic_namespace(self) -> bool:
384
- return self.require_static_namespace and dynamic_namespace_access(
385
- self.context.nodes(ast.AST), self.context.imports, self.context.parents
386
- )
397
+ return self.require_static_namespace and self.context.dynamic_namespace
387
398
 
388
399
  def _stable_async_definition(self, function: ast.AsyncFunctionDef) -> ast.AsyncFunctionDef | None:
389
- owner = self.context.parents.get(function)
400
+ owner = self.parents.get(function)
390
401
  if not isinstance(owner, ast.Module | ast.FunctionDef | ast.AsyncFunctionDef):
391
402
  return None
392
403
  if function.decorator_list or has_own_yield(function):
@@ -415,10 +426,10 @@ class ResourceProvenance:
415
426
  return True
416
427
 
417
428
  def _function_alias(self, read: ast.Name) -> ast.Name | None:
418
- parent = self.context.parents.get(read)
429
+ parent = self.parents.get(read)
419
430
  if not isinstance(parent, ast.Assign | ast.AnnAssign) or parent.value is not read:
420
431
  return None
421
- if not isinstance(self.context.parents.get(parent), ast.Module | ast.FunctionDef | ast.AsyncFunctionDef):
432
+ if not isinstance(self.parents.get(parent), ast.Module | ast.FunctionDef | ast.AsyncFunctionDef):
422
433
  return None
423
434
  targets = parent.targets if isinstance(parent, ast.Assign) else [parent.target]
424
435
  if len(targets) != 1 or not isinstance(targets[0], ast.Name):
@@ -427,7 +438,7 @@ class ResourceProvenance:
427
438
  return target if self.binding(target.id, target) is target else None
428
439
 
429
440
  def _safe_async_read(self, read: ast.Name) -> bool:
430
- parent = self.context.parents.get(read)
441
+ parent = self.parents.get(read)
431
442
  if not isinstance(parent, ast.Call):
432
443
  return False
433
444
  if parent.func is read:
@@ -469,7 +480,7 @@ class ResourceProvenance:
469
480
  return self.escape_cache[key]
470
481
 
471
482
  def _method_or_context_read(self, read: ast.Name, *, allow_context: bool) -> bool:
472
- parent = self.context.parents.get(read)
483
+ parent = self.parents.get(read)
473
484
  if allow_context and isinstance(parent, ast.withitem) and parent.context_expr is read:
474
485
  return True
475
486
  if (
@@ -478,12 +489,12 @@ class ResourceProvenance:
478
489
  or parent.attr in {"__setattr__", "__delattr__", "__getattribute__"}
479
490
  ):
480
491
  return False
481
- call = self.context.parents.get(parent)
492
+ call = self.parents.get(parent)
482
493
  return isinstance(call, ast.Call) and call.func is parent
483
494
 
484
495
  def contains(self, owner: ast.AST | None, node: ast.AST) -> bool:
485
- while node in self.context.parents:
486
- node = self.context.parents[node]
496
+ while node in self.parents:
497
+ node = self.parents[node]
487
498
  if node is owner:
488
499
  return True
489
500
  return False
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import ast
4
+ import re
4
5
  from typing import TYPE_CHECKING
5
6
 
6
7
  from sarj_python_lint.rules._paths import is_test_path
@@ -10,6 +11,15 @@ if TYPE_CHECKING:
10
11
  from pathlib import Path
11
12
 
12
13
 
14
+ _SQL_LINE_END = re.compile(r"[\r\n]")
15
+ _CONTINUED_QUOTE = re.compile(r"[ \t\f\v]*(?:--[^\r\n]*)?[\r\n](?:[ \t\f\v\r\n]|--[^\r\n]*[\r\n])*'")
16
+ POSTGRES_IDENTIFIER_CONTINUATION = r"[A-Za-z0-9_$\u0080-\U0010ffff]"
17
+ POSTGRES_UNQUOTED_IDENTIFIER = rf"[A-Za-z_\u0080-\U0010ffff]{POSTGRES_IDENTIFIER_CONTINUATION}*"
18
+ SQL_WHITESPACE = " \t\r\n\f\v"
19
+ _IDENTIFIER_CONTINUATION = re.compile(POSTGRES_IDENTIFIER_CONTINUATION)
20
+ _DOLLAR_DELIMITER = re.compile(r"\$(?:[A-Za-z_\u0080-\U0010ffff][A-Za-z0-9_\u0080-\U0010ffff]*)?\$")
21
+
22
+
13
23
  def is_store_module(path: Path) -> bool:
14
24
  if is_test_path(path):
15
25
  return False
@@ -44,6 +54,7 @@ def strip_sql_noise(
44
54
  *,
45
55
  mask_dollar_quotes: bool = True,
46
56
  mask_double_quotes: bool = True,
57
+ preserve_quoted_tokens: bool = False,
47
58
  ) -> str:
48
59
  out = list(text)
49
60
  i = 0
@@ -56,8 +67,10 @@ def strip_sql_noise(
56
67
  i += 1
57
68
  continue
58
69
  for offset in range(i, end):
59
- if text[offset] != "\n":
70
+ if text[offset] not in {"\n", "\r"}:
60
71
  out[offset] = " "
72
+ if preserve_quoted_tokens and text[i] in {"'", '"', "$"}:
73
+ out[i] = "?"
61
74
  i = end
62
75
  return "".join(out)
63
76
 
@@ -68,7 +81,7 @@ def _quoted_sql_end(text: str, start: int) -> int:
68
81
  quote == "'"
69
82
  and start > 0
70
83
  and text[start - 1] in {"E", "e"}
71
- and (start == 1 or not (text[start - 2].isalnum() or text[start - 2] in {"_", "$"}))
84
+ and (start == 1 or _IDENTIFIER_CONTINUATION.match(text, start - 2) is None)
72
85
  )
73
86
  i = start + 1
74
87
  while i < len(text):
@@ -82,6 +95,9 @@ def _quoted_sql_end(text: str, start: int) -> int:
82
95
  if i < len(text) and text[i] == quote:
83
96
  i += 1
84
97
  continue
98
+ if escape_string and (continuation := _CONTINUED_QUOTE.match(text, i)) is not None:
99
+ i = continuation.end()
100
+ continue
85
101
  break
86
102
  return i
87
103
 
@@ -91,8 +107,8 @@ def _sql_noise_end(text: str, start: int, *, mask_dollar_quotes: bool) -> int |
91
107
  if ch in {"'", '"'}:
92
108
  return _quoted_sql_end(text, start)
93
109
  if text.startswith("--", start) or (ch == "#" and (start == 0 or text[start - 1].isspace())):
94
- end = text.find("\n", start)
95
- return len(text) if end == -1 else end
110
+ end = _SQL_LINE_END.search(text, start)
111
+ return len(text) if end is None else end.start()
96
112
  if text.startswith("/*", start):
97
113
  return _block_comment_end(text, start)
98
114
  if mask_dollar_quotes and ch == "$":
@@ -116,17 +132,11 @@ def _block_comment_end(text: str, start: int) -> int:
116
132
 
117
133
 
118
134
  def _dollar_quote_end(text: str, start: int) -> int | None:
119
- if start > 0 and (text[start - 1].isalnum() or text[start - 1] in {"_", "$"}):
135
+ if start > 0 and _IDENTIFIER_CONTINUATION.match(text, start - 1) is not None:
120
136
  return None
121
- delimiter_end = text.find("$", start + 1)
122
- if delimiter_end == -1:
123
- return None
124
- tag = text[start + 1 : delimiter_end]
125
- valid_tag = not tag or (
126
- (tag[0].isalpha() or tag[0] == "_") and all(char.isalnum() or char == "_" for char in tag[1:])
127
- )
128
- if not valid_tag:
137
+ match = _DOLLAR_DELIMITER.match(text, start)
138
+ if match is None:
129
139
  return None
130
- delimiter = text[start : delimiter_end + 1]
131
- close = text.find(delimiter, delimiter_end + 1)
140
+ delimiter = match[0]
141
+ close = text.find(delimiter, match.end())
132
142
  return None if close == -1 else close + len(delimiter)
@@ -12,7 +12,6 @@ from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics, sc
12
12
  from sarj_python_lint.rules._ast_index import walk
13
13
  from sarj_python_lint.rules._paths import is_generated
14
14
  from sarj_python_lint.rules._project_index import ProjectIndexSet
15
- from sarj_python_lint.rules._resource_provenance import ResourceProvenance
16
15
 
17
16
 
18
17
  if TYPE_CHECKING:
@@ -20,6 +19,7 @@ if TYPE_CHECKING:
20
19
 
21
20
  from sarj_python_lint._file_context import PythonFileContext
22
21
  from sarj_python_lint.rules._imports import ImportIndex
22
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance
23
23
 
24
24
 
25
25
  type Function = ast.FunctionDef | ast.AsyncFunctionDef
@@ -187,7 +187,7 @@ def _comprehension_scopes(
187
187
  class StoreOwners:
188
188
  def __init__(self, context: PythonFileContext) -> None:
189
189
  self.context = context
190
- self.provenance = ResourceProvenance(context, require_static_namespace=True)
190
+ self.provenance = context.static_resource_provenance
191
191
  self.project = context.session.project
192
192
 
193
193
  def receiver(self, expression: ast.expr, at: ast.AST) -> tuple[ast.ClassDef, ImportIndex] | None:
@@ -290,7 +290,7 @@ class StoreOwners:
290
290
  from sarj_python_lint._file_context import PythonFileContext # ruff: ignore[import-outside-top-level] -- defer to avoid the rule registry's context import cycle.
291
291
 
292
292
  definition = PythonFileContext(owner.path, owner.source, self.context.session)
293
- provenance = ResourceProvenance(definition, require_static_namespace=True)
293
+ provenance = definition.static_resource_provenance
294
294
  if (
295
295
  provenance.dynamic_namespace
296
296
  or provenance.wildcard
@@ -16,11 +16,11 @@ from sarj_python_lint.rule_base import (
16
16
  Severity,
17
17
  is_suppressed,
18
18
  )
19
- from sarj_python_lint.rules._resource_provenance import ResourceProvenance
20
19
 
21
20
 
22
21
  if TYPE_CHECKING:
23
22
  from sarj_python_lint._file_context import PythonFileContext
23
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance
24
24
 
25
25
 
26
26
  _BAD = "from contextlib import AsyncExitStack\nasync def close():\n await release()\nasync def run():\n async with AsyncExitStack() as stack:\n stack.callback(close)\n"
@@ -72,7 +72,7 @@ class AsyncCleanupRegisteredSynchronously(Rule):
72
72
  def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
73
73
  if "callback" not in context.symbol_source or context.generated or context.tree is None:
74
74
  return []
75
- provenance = ResourceProvenance(context, require_static_namespace=True)
75
+ provenance = context.static_resource_provenance
76
76
  findings: list[Diagnostic] = []
77
77
  for call in context.nodes(ast.Call):
78
78
  if not _wrong_callback(call, provenance) or is_suppressed(context.source_lines, call.lineno, self.code):
@@ -21,7 +21,7 @@ from sarj_python_lint.rule_base import (
21
21
  from sarj_python_lint.rules._ast_index import walk as walk_ast
22
22
  from sarj_python_lint.rules._imports import ImportIndex
23
23
  from sarj_python_lint.rules._pytest import collected_tests
24
- from sarj_python_lint.rules._resource_provenance import ResourceProvenance, builtin_namespace_untouched
24
+ from sarj_python_lint.rules._resource_provenance import builtin_namespace_untouched
25
25
 
26
26
 
27
27
  if TYPE_CHECKING:
@@ -291,7 +291,7 @@ def _test_diagnostics(
291
291
 
292
292
 
293
293
  def _mock_provider_proven(test: _TestFunction, context: PythonFileContext) -> bool:
294
- provenance = ResourceProvenance(context, require_static_namespace=True)
294
+ provenance = context.static_resource_provenance
295
295
  constructors = [
296
296
  node
297
297
  for node in ast.walk(test)
@@ -26,13 +26,13 @@ from sarj_python_lint.rule_base import (
26
26
  from sarj_python_lint.rules._closed_helpers import dynamic_namespace, stable_class
27
27
  from sarj_python_lint.rules._paths import is_generated
28
28
  from sarj_python_lint.rules._project_index import ProjectIndexSet
29
- from sarj_python_lint.rules._resource_provenance import ResourceProvenance
30
29
  from sarj_python_lint.rules._suppression_comments import scan_comments
31
30
 
32
31
 
33
32
  if TYPE_CHECKING:
34
33
  from sarj_python_lint._file_context import PythonFileContext
35
34
  from sarj_python_lint.rules._project_index import SourceUnit, SymbolRef
35
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance
36
36
  from sarj_python_lint.rules._suppression_comments import Comment
37
37
 
38
38
 
@@ -85,7 +85,7 @@ class DuplicateAdjacentExceptionHandler(ProjectRule):
85
85
  return []
86
86
  project = context.session.project or ProjectIndexSet.single(context.path, context.source)
87
87
  unit = project.unit_or_source(context.path, context.source, tree)
88
- provenance = ResourceProvenance(context)
88
+ provenance = context.resource_provenance
89
89
  if not provenance.builtin_namespace_untouched:
90
90
  return []
91
91
  comments = scan_comments(context.source)
@@ -198,7 +198,7 @@ def _first_party_exception(
198
198
  definition_context = (
199
199
  context if owner.path == context.path else PythonFileContext(owner.path, owner.source, context.session)
200
200
  )
201
- definition_provenance = provenance if definition_context is context else ResourceProvenance(definition_context)
201
+ definition_provenance = provenance if definition_context is context else definition_context.resource_provenance
202
202
  declaration = next(
203
203
  (node for node in definition_context.nodes(ast.ClassDef) if node.name == summary.symbol.name), None
204
204
  )
@@ -17,11 +17,11 @@ from sarj_python_lint.rule_base import (
17
17
  is_suppressed,
18
18
  )
19
19
  from sarj_python_lint.rules._closed_helpers import call_matches, closed_references, direct_call, executable_body
20
- from sarj_python_lint.rules._resource_provenance import ResourceProvenance
21
20
 
22
21
 
23
22
  if TYPE_CHECKING:
24
23
  from sarj_python_lint._file_context import PythonFileContext
24
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance
25
25
 
26
26
 
27
27
  _HELPER = "def _space(match: re.Match[str]) -> str:\n full = match.group(0)\n digits = match.group(1)\n spaced = ' '.join(digits)\n return full.replace(digits, spaced)\n"
@@ -92,7 +92,7 @@ class DuplicateClosedPureHelper(Rule):
92
92
  tree = context.tree
93
93
  if context.generated or tree is None or ".group" not in context.symbol_source:
94
94
  return []
95
- provenance = ResourceProvenance(context)
95
+ provenance = context.resource_provenance
96
96
  owners: dict[str, ast.FunctionDef] = {}
97
97
  findings: list[Diagnostic] = []
98
98
  for function in tree.body:
@@ -103,7 +103,7 @@ class NamedRecordAtBoundaries(Rule):
103
103
  source_lines = context.source_lines
104
104
  local = _local_function_ids(tree)
105
105
  private_class_methods = _private_class_function_ids(tree)
106
- semantics = AnnotationSemantics.from_tree(tree)
106
+ semantics = context.annotation_semantics
107
107
  imports = semantics.imports
108
108
  fastapi = context.fastapi
109
109
  class_shadowed_annotations = _class_shadowed_annotation_ids(tree, semantics, node_index=context.node_index)