sarj-python-lint 1.77.2__tar.gz → 1.77.3__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 (272) hide show
  1. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/__init__.py +4 -0
  4. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_file_context.py +2 -2
  5. sarj_python_lint-1.77.3/src/sarj_python_lint/application_logging.py +379 -0
  6. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_copy_on_write.py +13 -2
  7. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_test_provenance.py +13 -2
  8. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/.gitignore +0 -0
  9. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/LICENSE +0 -0
  10. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/README.md +0 -0
  11. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/__main__.py +0 -0
  12. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_analysis_session.py +0 -0
  13. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix.py +0 -0
  14. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_aliases.py +0 -0
  15. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_annotations.py +0 -0
  16. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_comments.py +0 -0
  17. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
  18. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
  19. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_docs.py +0 -0
  20. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_enums.py +0 -0
  21. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
  22. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_inert.py +0 -0
  23. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_parameters.py +0 -0
  24. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_repeated.py +0 -0
  25. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_security.py +0 -0
  26. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
  27. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_tests.py +0 -0
  28. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_filesystem.py +0 -0
  29. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_fixes.py +0 -0
  30. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_parallel.py +0 -0
  31. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_python_target.py +0 -0
  32. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  33. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_secret_names.py +0 -0
  34. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_source.py +0 -0
  35. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_version.py +0 -0
  36. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/autofix.py +0 -0
  37. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/benchmark.py +0 -0
  38. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/cast_access.py +0 -0
  39. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/interpreter_argv.py +0 -0
  40. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/json_boundary.py +0 -0
  41. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/py.typed +0 -0
  42. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/ratchet.py +0 -0
  43. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rule_base.py +0 -0
  44. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/__init__.py +0 -0
  45. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
  46. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  47. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  48. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_ast_names.py +0 -0
  49. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  50. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  51. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_comments.py +0 -0
  52. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  53. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  54. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
  55. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  56. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  57. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_exports.py +0 -0
  58. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  59. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_first_party.py +0 -0
  60. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  61. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_imports.py +0 -0
  62. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  63. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  64. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
  65. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_local_source.py +0 -0
  66. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_logging.py +0 -0
  67. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  68. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  69. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  70. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  71. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_paths.py +0 -0
  72. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_project_index.py +0 -0
  73. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  74. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  75. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_pytest.py +0 -0
  76. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_registry.py +0 -0
  77. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  78. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  79. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  80. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  81. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  82. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_sql.py +0 -0
  83. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  84. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_store_writes.py +0 -0
  85. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  86. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  87. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  88. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  89. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  90. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  91. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  92. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  93. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  94. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  95. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  96. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  97. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  98. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
  99. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  100. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  101. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  102. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  103. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  104. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  105. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  106. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  107. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  108. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  109. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  110. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  111. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  112. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  113. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  114. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  115. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  116. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  117. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  118. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  119. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  120. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  121. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  122. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  123. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  124. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  125. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  126. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  127. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  128. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  129. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  130. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  131. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  132. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  133. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  134. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  135. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  136. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  137. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  138. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  139. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  140. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  141. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  142. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  143. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  144. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  145. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  146. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  147. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  148. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  149. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  150. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  151. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  152. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  153. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  154. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  155. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  156. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  157. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  158. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  159. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  160. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  161. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  162. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
  163. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  164. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  165. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  166. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  167. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  168. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  169. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  170. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  171. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  172. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  173. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  174. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  175. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  176. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  177. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  178. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  179. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  180. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  181. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  182. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  183. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  184. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  185. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  186. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  187. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  188. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  189. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  190. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  191. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  192. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  193. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  194. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  195. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  196. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
  197. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  198. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  199. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
  200. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  201. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  202. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  203. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  204. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  205. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  206. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  207. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  208. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  209. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  210. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  211. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  212. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  213. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  214. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  215. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
  216. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  217. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  218. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
  219. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
  220. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  221. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  222. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  223. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  224. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  225. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  226. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  227. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  228. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
  229. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  230. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  231. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  232. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  233. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  234. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  235. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
  236. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  237. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  238. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  239. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  240. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  241. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  242. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  243. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  244. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  245. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  246. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  247. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  248. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  249. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  250. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  251. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  252. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  253. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  254. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  255. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
  256. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  257. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  258. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  259. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  260. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/stepdown.py +0 -0
  261. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  262. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  263. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  264. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  265. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  266. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  267. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  268. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  269. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  270. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  271. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
  272. {sarj_python_lint-1.77.2 → sarj_python_lint-1.77.3}/src/sarj_python_lint/suppression_policy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 1.77.2
3
+ Version: 1.77.3
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.77.2"
3
+ version = "1.77.3"
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" }]
@@ -3,3 +3,7 @@ from sarj_python_lint._fixes import apply_fixes as apply_fixes, python_source_ha
3
3
  from sarj_python_lint._ratchet_cli import run_ratchet as run_ratchet
4
4
  from sarj_python_lint._source import python_source_encoding as python_source_encoding
5
5
  from sarj_python_lint._version import __version__ as __version__
6
+ from sarj_python_lint.application_logging import (
7
+ ApplicationLoggingUse as ApplicationLoggingUse,
8
+ application_logging_uses as application_logging_uses,
9
+ )
@@ -109,7 +109,7 @@ class PythonFileContext:
109
109
  def test_provenance(self) -> TestProvenance:
110
110
  from sarj_python_lint.rules._test_provenance import TestProvenance # ruff: ignore[import-outside-top-level] — defer test analysis until requested
111
111
 
112
- return TestProvenance(self)
112
+ return TestProvenance(self, borrow_context=True)
113
113
 
114
114
  def _valid_tree(self) -> ast.Module:
115
115
  tree = self.tree
@@ -122,7 +122,7 @@ class PythonFileContext:
122
122
  def copy_on_write(self) -> CopyOnWriteAnalysis:
123
123
  from sarj_python_lint.rules._copy_on_write import CopyOnWriteAnalysis # ruff: ignore[import-outside-top-level] — share ownership analysis only when requested
124
124
 
125
- return CopyOnWriteAnalysis(self)
125
+ return CopyOnWriteAnalysis(self, borrow_context=True)
126
126
 
127
127
  @cached_property
128
128
  def resource_provenance(self) -> ResourceProvenance:
@@ -0,0 +1,379 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING, Literal
6
+
7
+
8
+ if TYPE_CHECKING:
9
+ from pathlib import Path
10
+
11
+ from sarj_python_lint._file_context import PythonFileContext
12
+
13
+
14
+ _PUBLIC_EMITTERS = frozenset({"debug", "info", "warning", "warn", "error", "exception", "critical", "fatal", "log"})
15
+ _MODULE_EMITTERS = frozenset("logging." + name for name in _PUBLIC_EMITTERS)
16
+ _LOGGER_FACTORIES = frozenset({"logging.getLogger", "logging.Logger"})
17
+ _BUILTIN_SINKS = frozenset({"logging.StreamHandler", "logging.FileHandler"})
18
+ _ROUTING_ATTRIBUTES = frozenset({"handlers", "filters", "level", "disabled", "propagate", "name"})
19
+ _ROUTING_METHODS = frozenset(
20
+ {
21
+ "setLevel",
22
+ "addHandler",
23
+ "removeHandler",
24
+ "addFilter",
25
+ "removeFilter",
26
+ "isEnabledFor",
27
+ "getEffectiveLevel",
28
+ "hasHandlers",
29
+ }
30
+ )
31
+ _SCOPES = (ast.Module, ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Lambda)
32
+ _MAX_SOURCE_CHARACTERS = 500_000
33
+ _MIN_ATTRIBUTE_MUTATION_ARGS = 2
34
+
35
+
36
+ @dataclass(frozen=True, slots=True)
37
+ class ApplicationLoggingUse:
38
+ line: int
39
+ column: int
40
+ operation: str
41
+ kind: Literal["event", "sink"]
42
+
43
+
44
+ @dataclass(frozen=True, slots=True)
45
+ class _ModuleImportState:
46
+ import_lines: dict[str, int]
47
+ star_import_line: int
48
+ module_names: frozenset[str]
49
+ root_names: frozenset[str]
50
+
51
+
52
+ @dataclass(frozen=True, slots=True)
53
+ class _LoggerBinding:
54
+ scope: ast.AST
55
+ name: str
56
+ assignment: ast.Assign | ast.AnnAssign
57
+
58
+
59
+ def application_logging_uses(path: Path, source: str, *, root: Path) -> tuple[ApplicationLoggingUse, ...]:
60
+ from sarj_python_lint._file_context import PythonFileContext # ruff: ignore[import-outside-top-level] -- load native AST helpers only when library policy selects Python source.
61
+ from sarj_python_lint.rules._paths import is_test_path # ruff: ignore[import-outside-top-level] -- preserve the engine-owned test scope without cross-package private imports.
62
+
63
+ if not path.is_absolute() or not root.is_absolute() or ".." in path.parts or ".." in root.parts:
64
+ msg = "logging policy requires canonical absolute source and root paths"
65
+ raise ValueError(msg)
66
+ try:
67
+ relative = path.relative_to(root)
68
+ except ValueError as exc:
69
+ msg = "logging policy source escapes repository root"
70
+ raise ValueError(msg) from exc
71
+ if relative.suffix not in {".py", ".pyi"} or len(source) > _MAX_SOURCE_CHARACTERS:
72
+ msg = "logging policy requires a bounded selected Python source"
73
+ raise ValueError(msg)
74
+ if relative.suffix == ".pyi":
75
+ return ()
76
+ context = PythonFileContext(path, source)
77
+ if is_test_path(relative) or context.generated:
78
+ return ()
79
+ if context.tree is None:
80
+ msg = f"selected Python source could not be parsed: {path}"
81
+ raise ValueError(msg)
82
+ return _LoggingProvenance(context).uses()
83
+
84
+
85
+ class _LoggingProvenance:
86
+ def __init__(self, context: PythonFileContext) -> None:
87
+ self.context = context
88
+ tree = context.tree
89
+ if tree is None:
90
+ msg = "logging provenance requires a parsed Python syntax tree"
91
+ raise ValueError(msg)
92
+ self.tree = tree
93
+ self.parents = context.parents
94
+ self.imports = context.imports
95
+ self.nodes = context.nodes(ast.AST)
96
+ self.import_state = self._module_import_state()
97
+ self.escaped_names = self._escaped_module_names()
98
+ self.mutated_operations: set[str] = set()
99
+ self._collect_mutations()
100
+ self.binding_counts: dict[tuple[ast.AST, str], int] = {}
101
+ self.binding_loads: dict[_LoggerBinding, list[ast.Name]] = {}
102
+ self.bindings = self._logger_bindings()
103
+
104
+ def uses(self) -> tuple[ApplicationLoggingUse, ...]:
105
+ findings = [use for call in self.context.nodes(ast.Call) if (use := self._call_use(call)) is not None]
106
+ return tuple(sorted(set(findings), key=lambda item: (item.line, item.column, item.operation)))
107
+
108
+ def _call_use(self, call: ast.Call) -> ApplicationLoggingUse | None:
109
+ qualified = self._qualified(call.func)
110
+ if qualified in _BUILTIN_SINKS:
111
+ return self._use(call, qualified, "sink")
112
+ if qualified == "logging.basicConfig" and self._uses_default_handlers(call):
113
+ return self._use(call, qualified, "sink")
114
+ if qualified in _MODULE_EMITTERS:
115
+ return self._use(call, qualified, "event")
116
+ if not isinstance(call.func, ast.Attribute) or call.func.attr not in _PUBLIC_EMITTERS:
117
+ return None
118
+ operation = "logging.Logger." + call.func.attr
119
+ if operation in self.mutated_operations or not self._logger_receiver(call.func.value):
120
+ return None
121
+ return self._use(call, operation, "event")
122
+
123
+ @staticmethod
124
+ def _uses_default_handlers(call: ast.Call) -> bool:
125
+ handlers = next((keyword.value for keyword in call.keywords if keyword.arg == "handlers"), None)
126
+ default_handlers = handlers is None or (isinstance(handlers, ast.Constant) and handlers.value is None)
127
+ return default_handlers and not any(keyword.arg is None for keyword in call.keywords)
128
+
129
+ def _use(self, call: ast.Call, operation: str, kind: Literal["event", "sink"]) -> ApplicationLoggingUse:
130
+ line = self.context.source_lines[call.lineno - 1]
131
+ column = len(line.encode("utf-8")[: call.col_offset].decode("utf-8")) + 1
132
+ return ApplicationLoggingUse(call.lineno, column, operation, kind)
133
+
134
+ def _scope(self, node: ast.AST) -> ast.AST:
135
+ current = self.parents.get(node)
136
+ while current is not None and not isinstance(current, _SCOPES):
137
+ current = self.parents.get(current)
138
+ return self.tree if current is None else current
139
+
140
+ def _module_import_state(self) -> _ModuleImportState:
141
+ lines: dict[str, int] = {}
142
+ star_line = 0
143
+ for statement in self.tree.body:
144
+ match statement:
145
+ case ast.Import():
146
+ for alias in statement.names:
147
+ lines[alias.asname or alias.name.partition(".")[0]] = statement.lineno
148
+ case ast.ImportFrom():
149
+ for alias in statement.names:
150
+ if alias.name == "*":
151
+ star_line = max(star_line, statement.lineno)
152
+ else:
153
+ lines[alias.asname or alias.name] = statement.lineno
154
+ case _:
155
+ pass
156
+ return _ModuleImportState(lines, star_line, self._logging_module_aliases(), self._logging_root_aliases())
157
+
158
+ def _logging_module_aliases(self) -> frozenset[str]:
159
+ return frozenset(
160
+ alias.asname or alias.name.partition(".")[0]
161
+ for statement in self.tree.body
162
+ if isinstance(statement, ast.Import)
163
+ for alias in statement.names
164
+ if alias.name == "logging" or alias.name.startswith("logging.")
165
+ )
166
+
167
+ def _logging_root_aliases(self) -> frozenset[str]:
168
+ return frozenset(
169
+ alias.asname or alias.name
170
+ for statement in self.tree.body
171
+ if isinstance(statement, ast.ImportFrom) and statement.level == 0 and statement.module == "logging"
172
+ for alias in statement.names
173
+ if alias.name == "root"
174
+ )
175
+
176
+ def _escaped_module_names(self) -> frozenset[str]:
177
+ module_names = self.import_state.module_names
178
+ root_names = self.import_state.root_names
179
+ imported_names = module_names | root_names
180
+ escaped: set[str] = set()
181
+ for node in self.context.nodes(ast.Name):
182
+ if node.id not in imported_names or not isinstance(node.ctx, ast.Load):
183
+ continue
184
+ parent = self.parents.get(node)
185
+ if not isinstance(parent, ast.Attribute):
186
+ escaped.add(node.id)
187
+ continue
188
+ if (node.id in module_names and parent.attr == "__dict__") or (
189
+ node.id in root_names and self._root_reference_escapes(parent)
190
+ ):
191
+ escaped.add(node.id)
192
+ escaped_targets = {
193
+ (target.module, target.symbol)
194
+ for name in escaped
195
+ if (target := self.imports.bindings.get(name)) is not None
196
+ }
197
+ return frozenset(
198
+ name
199
+ for name in imported_names
200
+ if name in escaped
201
+ or (
202
+ (target := self.imports.bindings.get(name)) is not None
203
+ and (target.module, target.symbol) in escaped_targets
204
+ )
205
+ )
206
+
207
+ def _root_reference_escapes(self, parent: ast.Attribute) -> bool:
208
+ if parent.attr not in _PUBLIC_EMITTERS | _ROUTING_METHODS:
209
+ return parent.attr not in _ROUTING_ATTRIBUTES
210
+ call = self.parents.get(parent)
211
+ return isinstance(parent.ctx, ast.Load) and (not isinstance(call, ast.Call) or call.func is not parent)
212
+
213
+ def _qualified(self, expression: ast.expr) -> str | None:
214
+ root = expression
215
+ while isinstance(root, ast.Attribute):
216
+ root = root.value
217
+ if not isinstance(root, ast.Name) or root.id in self.escaped_names:
218
+ return None
219
+ if self.import_state.import_lines.get(root.id, 0) <= self.import_state.star_import_line:
220
+ return None
221
+ name = self.imports.resolved_qualified_name(expression)
222
+ if name is None:
223
+ return None
224
+ parts = name.split(".")
225
+ if any(".".join(parts[:end]) in self.mutated_operations for end in range(1, len(parts) + 1)):
226
+ return None
227
+ return name
228
+
229
+ def _collect_mutations(self) -> None:
230
+ for node in self.context.nodes(ast.Attribute):
231
+ if node.attr == "__dict__" and (
232
+ self._factory_receiver(node.value) or self.imports.resolved_qualified_name(node.value) == "logging.root"
233
+ ):
234
+ self.mutated_operations.update("logging.Logger." + name for name in _PUBLIC_EMITTERS)
235
+ if isinstance(node.ctx, (ast.Store, ast.Del)):
236
+ self._record_attribute_mutation(node)
237
+ for call in self.context.nodes(ast.Call):
238
+ attribute = self._mutation_call_attribute(call)
239
+ if attribute is not None:
240
+ self._record_attribute_mutation(attribute)
241
+
242
+ def _record_attribute_mutation(self, attribute: ast.Attribute) -> None:
243
+ qualified = self._qualified(attribute)
244
+ if self._factory_receiver(attribute.value):
245
+ qualified = "logging.Logger." + attribute.attr
246
+ if qualified is not None:
247
+ self.mutated_operations.add(qualified)
248
+
249
+ def _mutation_call_attribute(self, call: ast.Call) -> ast.Attribute | None:
250
+ if not isinstance(call.func, ast.Name) or call.func.id not in {"setattr", "delattr"}:
251
+ return None
252
+ if not self.imports.builtin_is_unshadowed(call.func.id) or len(call.args) < _MIN_ATTRIBUTE_MUTATION_ARGS:
253
+ return None
254
+ name = call.args[1]
255
+ if not isinstance(name, ast.Constant) or not isinstance(name.value, str):
256
+ return None
257
+ return ast.Attribute(value=call.args[0], attr=name.value, ctx=ast.Load())
258
+
259
+ def _factory_receiver(self, expression: ast.expr) -> bool:
260
+ return (
261
+ isinstance(expression, ast.Call) and self._qualified(expression.func) in _LOGGER_FACTORIES
262
+ ) or self._qualified(expression) == "logging.root"
263
+
264
+ def _logger_bindings(self) -> dict[tuple[ast.AST, str], _LoggerBinding]:
265
+ assignments = self._index_bindings()
266
+ candidates: dict[tuple[ast.AST, str], _LoggerBinding] = {}
267
+ for binding in assignments:
268
+ if self._is_logger_binding(binding):
269
+ candidates[binding.scope, binding.name] = binding
270
+ self.bindings = candidates
271
+ self.binding_loads = {}
272
+ for node in self.context.nodes(ast.Name):
273
+ if isinstance(node.ctx, ast.Load) and (binding := self._binding(node)) is not None:
274
+ self.binding_loads.setdefault(binding, []).append(node)
275
+ return {key: binding for key, binding in candidates.items() if self._nonescaping(binding)}
276
+
277
+ def _index_bindings(self) -> list[_LoggerBinding]:
278
+ counts: dict[tuple[ast.AST, str], int] = {}
279
+ assignments: list[_LoggerBinding] = []
280
+ for node in self.nodes:
281
+ name = self._bound_name(node)
282
+ if name is not None:
283
+ key = (self._scope(node), name)
284
+ counts[key] = counts.get(key, 0) + 1
285
+ match node:
286
+ case ast.Global() | ast.Nonlocal():
287
+ for name in node.names:
288
+ counts[self._scope(node), name] = 2
289
+ case ast.Import() | ast.ImportFrom():
290
+ for alias in node.names:
291
+ local = alias.asname or alias.name.partition(".")[0]
292
+ key = (self._scope(node), local)
293
+ counts[key] = counts.get(key, 0) + 1
294
+ case _:
295
+ pass
296
+ assignment = self._logger_assignment(node)
297
+ if assignment is not None:
298
+ assignments.append(assignment)
299
+ self.binding_counts = counts
300
+ return assignments
301
+
302
+ @staticmethod
303
+ def _bound_name(node: ast.AST) -> str | None:
304
+ match node:
305
+ case ast.Name(id=name, ctx=ast.Store() | ast.Del()):
306
+ return name
307
+ case ast.arg(arg=name):
308
+ return name
309
+ case (
310
+ ast.FunctionDef(name=name)
311
+ | ast.AsyncFunctionDef(name=name)
312
+ | ast.ClassDef(name=name)
313
+ | ast.ExceptHandler(name=name)
314
+ | ast.MatchAs(name=name)
315
+ | ast.MatchStar(name=name)
316
+ ):
317
+ return name
318
+ case ast.MatchMapping(rest=name):
319
+ return name
320
+ case _:
321
+ pass
322
+ return None
323
+
324
+ def _logger_assignment(self, node: ast.AST) -> _LoggerBinding | None:
325
+ match node:
326
+ case ast.Assign(targets=[ast.Name(id=name)]):
327
+ return _LoggerBinding(self._scope(node), name, node)
328
+ case ast.AnnAssign(target=ast.Name(id=name), value=value) if value is not None:
329
+ return _LoggerBinding(self._scope(node), name, node)
330
+ case _:
331
+ pass
332
+ return None
333
+
334
+ def _is_logger_binding(self, binding: _LoggerBinding) -> bool:
335
+ assignment = binding.assignment
336
+ if binding.scope is self.tree and assignment.lineno <= self.import_state.star_import_line:
337
+ return False
338
+ if self.binding_counts.get((binding.scope, binding.name)) != 1:
339
+ return False
340
+ if self.parents.get(assignment) is not binding.scope:
341
+ return False
342
+ return isinstance(assignment.value, ast.Call) and self._qualified(assignment.value.func) in _LOGGER_FACTORIES
343
+
344
+ def _binding(self, node: ast.Name) -> _LoggerBinding | None:
345
+ scope = self._scope(node)
346
+ origin = scope
347
+ while scope is not None:
348
+ if not (isinstance(scope, ast.ClassDef) and scope is not origin):
349
+ key = (scope, node.id)
350
+ if key in self.binding_counts:
351
+ return self.bindings.get(key)
352
+ parent = self._scope(scope)
353
+ scope = None if parent is scope else parent
354
+ return None
355
+
356
+ def _nonescaping(self, binding: _LoggerBinding) -> bool:
357
+ for node in self.binding_loads.get(binding, ()):
358
+ parent = self.parents.get(node)
359
+ if not isinstance(parent, ast.Attribute):
360
+ return False
361
+ if parent.attr in _PUBLIC_EMITTERS:
362
+ call = self.parents.get(parent)
363
+ if not isinstance(parent.ctx, ast.Load) or not isinstance(call, ast.Call) or call.func is not parent:
364
+ return False
365
+ elif parent.attr in _ROUTING_METHODS:
366
+ call = self.parents.get(parent)
367
+ if not isinstance(call, ast.Call) or call.func is not parent:
368
+ return False
369
+ elif parent.attr not in _ROUTING_ATTRIBUTES:
370
+ return False
371
+ return True
372
+
373
+ def _logger_receiver(self, expression: ast.expr) -> bool:
374
+ if self._factory_receiver(expression):
375
+ return True
376
+ if isinstance(expression, ast.Name):
377
+ binding = self._binding(expression)
378
+ return binding is not None and binding.assignment.lineno < expression.lineno
379
+ return False
@@ -5,6 +5,7 @@ from dataclasses import dataclass, replace
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._analysis_session import AnalysisSession
10
11
  from sarj_python_lint._file_context import PythonFileContext
@@ -359,8 +360,10 @@ def _contains_model(shape: _Shape) -> bool:
359
360
 
360
361
  @final
361
362
  class CopyOnWriteAnalysis:
362
- def __init__(self, context: PythonFileContext) -> None:
363
- self.context = context
363
+ def __init__(self, context: PythonFileContext, *, borrow_context: bool = False) -> None:
364
+ self._context: PythonFileContext | ReferenceType[PythonFileContext] = (
365
+ ref(context) if borrow_context else context
366
+ )
364
367
  self.models = _Models(context)
365
368
  self.mutations: list[Mutation] = []
366
369
  self.discarded: list[ast.Call] = []
@@ -374,6 +377,14 @@ class CopyOnWriteAnalysis:
374
377
  for function in context.nodes(ast.FunctionDef, ast.AsyncFunctionDef):
375
378
  self._function(function)
376
379
 
380
+ @property
381
+ def context(self) -> PythonFileContext:
382
+ context = self._context() if isinstance(self._context, ReferenceType) else self._context
383
+ if context is None:
384
+ msg = "The copy-on-write file context has been released"
385
+ raise ReferenceError(msg)
386
+ return context
387
+
377
388
  @cached_property
378
389
  def _validator_provider(self) -> ResourceProvenance:
379
390
  return self.context.static_resource_provenance
@@ -5,6 +5,7 @@ from dataclasses import dataclass, field
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 walk
10
11
  from sarj_python_lint.rules._imports import ImportIndex
@@ -57,8 +58,10 @@ class _Analysis:
57
58
 
58
59
  @final
59
60
  class TestProvenance:
60
- def __init__(self, context: PythonFileContext) -> None:
61
- self.context = context
61
+ def __init__(self, context: PythonFileContext, *, borrow_context: bool = False) -> None:
62
+ self._context: PythonFileContext | ReferenceType[PythonFileContext] = (
63
+ ref(context) if borrow_context else context
64
+ )
62
65
  self.indexes = context.session.project or ProjectIndexSet.single(context.path, context.source)
63
66
  tree = context.tree
64
67
  indexed_unit = (
@@ -90,6 +93,14 @@ class TestProvenance:
90
93
  self._grafts: list[tuple[ast.AST, str]] = []
91
94
  self._restoring: dict[tuple[str, ...], str] = {}
92
95
 
96
+ @property
97
+ def context(self) -> PythonFileContext:
98
+ context = self._context() if isinstance(self._context, ReferenceType) else self._context
99
+ if context is None:
100
+ msg = "The test-provenance file context has been released"
101
+ raise ReferenceError(msg)
102
+ return context
103
+
93
104
  def method_grafts(self) -> list[tuple[ast.AST, str]]:
94
105
  return self._analyzed.grafts
95
106