sarj-python-lint 1.108.52__tar.gz → 1.109.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 (323) hide show
  1. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/__main__.py +6 -3
  4. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix.py +155 -32
  5. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_comments.py +14 -9
  6. sarj_python_lint-1.109.0/src/sarj_python_lint/_autofix_sql_projection.py +223 -0
  7. sarj_python_lint-1.108.52/src/sarj_python_lint/_autofix_sql_projection.py → sarj_python_lint-1.109.0/src/sarj_python_lint/_closed_sql_literal.py +3 -89
  8. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_parallel.py +4 -0
  9. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_postgres_ctas.py +26 -2
  10. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_runtime_names.py +14 -0
  11. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/excessive_commentary.py +1 -1
  12. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_dynamic_builtins.py +1 -1
  13. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_redundant_sql_alias.py +48 -7
  14. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_test_comments.py +46 -2
  15. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +5 -5
  16. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_contract.py +14 -3
  17. sarj_python_lint-1.109.0/src/sarj_python_lint/rules/prefer_typed_cli.py +585 -0
  18. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/stepdown.py +145 -1
  19. sarj_python_lint-1.108.52/src/sarj_python_lint/rules/prefer_typed_cli.py +0 -233
  20. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/.gitignore +0 -0
  21. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/LICENSE +0 -0
  22. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/README.md +0 -0
  23. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/__init__.py +0 -0
  24. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  25. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
  26. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
  27. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
  28. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
  29. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_docs.py +0 -0
  30. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_enums.py +0 -0
  31. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
  32. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_inert.py +0 -0
  33. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
  34. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_repeated.py +0 -0
  35. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_security.py +0 -0
  36. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
  37. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_file_context.py +0 -0
  38. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_filesystem.py +0 -0
  39. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_fixes.py +0 -0
  40. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_prepared_sources.py +0 -0
  41. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_python_target.py +0 -0
  42. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  43. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_rule_descriptors.py +0 -0
  44. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_secret_names.py +0 -0
  45. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_source.py +0 -0
  46. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_version.py +0 -0
  47. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/application_logging.py +0 -0
  48. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/autofix.py +0 -0
  49. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/benchmark.py +0 -0
  50. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/cast_access.py +0 -0
  51. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  52. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/json_boundary.py +0 -0
  53. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/py.typed +0 -0
  54. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/ratchet.py +0 -0
  55. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rule_base.py +0 -0
  56. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rule_metadata.py +0 -0
  57. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  58. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
  59. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  60. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  61. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
  62. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  63. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_async_lifecycle.py +0 -0
  64. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_binding_inventory.py +0 -0
  65. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  66. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
  67. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  68. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  69. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  70. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  71. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
  72. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  73. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  74. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  75. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  76. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  77. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  78. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_fixture_contracts.py +0 -0
  79. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  80. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  81. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  82. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
  83. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  84. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  85. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_loguru.py +0 -0
  86. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  87. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  88. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  89. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
  90. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  91. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_numeric_proof.py +0 -0
  92. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_opaque_contracts.py +0 -0
  93. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  94. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_postgres.py +0 -0
  95. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  96. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  97. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  98. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  99. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_pytest_contract_parameters.py +0 -0
  100. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_registry.py +0 -0
  101. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  102. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  103. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  104. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  105. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_runtime_registry.py +0 -0
  106. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
  107. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  108. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  109. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
  110. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
  111. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
  112. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  113. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_store_sql.py +0 -0
  114. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
  115. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  116. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  117. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  118. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  119. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
  120. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  121. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  122. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/async_executor_shutdown.py +0 -0
  123. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  124. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +0 -0
  125. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +0 -0
  126. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  127. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  128. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  129. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  130. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  131. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  132. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  133. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
  134. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +0 -0
  135. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  136. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  137. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  138. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  139. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  140. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  141. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  142. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +0 -0
  143. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  144. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  145. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  146. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_async_client_in_constructor.py +0 -0
  147. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  148. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  149. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  150. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  151. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  152. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  153. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  154. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  155. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  156. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  157. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  158. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_custom_control_flow_exception.py +0 -0
  159. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  160. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  161. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  162. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_discarded_tagged_outcome.py +0 -0
  163. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  164. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  165. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  166. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  167. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  168. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  169. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  170. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  171. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  172. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  173. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  174. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  175. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  176. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  177. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_generic_json_parser.py +0 -0
  178. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  179. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  180. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  181. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  182. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  183. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  184. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  185. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  186. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  187. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  188. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  189. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  190. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  191. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  192. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_mutating_input_with_result.py +0 -0
  193. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  194. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  195. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  196. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  197. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  198. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  199. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  200. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  201. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  202. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_recording_store.py +0 -0
  203. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_redundant_attribute_unpacking.py +0 -0
  204. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  205. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
  206. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  207. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  208. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  209. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  210. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  211. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  212. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  213. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  214. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  215. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_shared_mutable_default.py +0 -0
  216. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  217. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  218. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  219. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_sys_path_mutation.py +0 -0
  220. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  221. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  222. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  223. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  224. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  225. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  226. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  227. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  228. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  229. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  230. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  231. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  232. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  233. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  234. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  235. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_application_model_joins.py +0 -0
  236. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  237. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  238. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  239. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  240. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  241. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  242. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
  243. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  244. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  245. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
  246. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  247. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  248. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  249. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  250. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  251. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  252. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  253. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  254. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  255. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  256. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  257. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  258. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  259. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_optional_composite.py +0 -0
  260. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  261. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
  262. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  263. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  264. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
  265. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
  266. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  267. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  268. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  269. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_runtime_validation.py +0 -0
  270. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  271. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  272. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  273. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  274. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  275. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +0 -0
  276. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
  277. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  278. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  279. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  280. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  281. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  282. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  283. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
  284. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  285. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  286. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  287. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  288. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  289. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  290. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  291. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  292. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_constrained_api_integer.py +0 -0
  293. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  294. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  295. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  296. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  297. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  298. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  299. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  300. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  301. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  302. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  303. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  304. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
  305. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  306. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  307. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  308. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/service_singleton_delegates_to_bulk.py +0 -0
  309. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  310. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  311. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  312. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/store_insert_requires_explicit_conflict_policy.py +0 -0
  313. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  314. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/store_mutation_command_fanout.py +0 -0
  315. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  316. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  317. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  318. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  319. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  320. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  321. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  322. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
  323. {sarj_python_lint-1.108.52 → sarj_python_lint-1.109.0}/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.108.52
3
+ Version: 1.109.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.108.52"
3
+ version = "1.109.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" }]
@@ -222,15 +222,18 @@ def check_source(
222
222
  ) -> list[Diagnostic]:
223
223
  from sarj_python_lint._file_context import PythonFileContext # ruff: ignore[import-outside-top-level] -- source checks own native AST context construction.
224
224
 
225
- context = PythonFileContext(p, source, session)
225
+ return check_context(rules, PythonFileContext(p, source, session))
226
+
227
+
228
+ def check_context(rules: list[Rule], context: PythonFileContext) -> list[Diagnostic]:
226
229
  if rules and context.tree is None:
227
- msg = f"selected Python source could not be parsed: {p}"
230
+ msg = f"selected Python source could not be parsed: {context.path}"
228
231
  raise ValueError(msg)
229
232
  source_lines = context.source_lines
230
233
  raw = [diagnostic for rule in rules for diagnostic in rule.check_context(context)]
231
234
  return deduplicate_diagnostics(
232
235
  [diagnostic for diagnostic in raw if not is_suppressed(source_lines, diagnostic.line, diagnostic.code)],
233
- source=source,
236
+ source=context.source,
234
237
  context=context,
235
238
  )
236
239
 
@@ -26,7 +26,7 @@ from sarj_python_lint._autofix_exhaustiveness import ExhaustivenessFixes
26
26
  from sarj_python_lint._autofix_parameters import ParameterFixes
27
27
  from sarj_python_lint._autofix_repeated import RepeatedLiteralFixes
28
28
  from sarj_python_lint._autofix_security import fixes as security_fixes
29
- from sarj_python_lint._autofix_sql_projection import fixes as sql_projection_fixes
29
+ from sarj_python_lint._autofix_sql_projection import alias_fixes as sql_alias_fixes, fixes as sql_projection_fixes
30
30
  from sarj_python_lint._autofix_tests import fixes as testing_fixes
31
31
  from sarj_python_lint._file_context import PythonFileContext
32
32
  from sarj_python_lint._fixes import apply_fixes as apply_native_fixes
@@ -41,6 +41,7 @@ from sarj_python_lint.rules import (
41
41
  from sarj_python_lint.rules._ast_index import object_list
42
42
  from sarj_python_lint.rules._closed_helpers import dynamic_namespace
43
43
  from sarj_python_lint.rules._resource_provenance import ResourceProvenance
44
+ from sarj_python_lint.rules.prefer_match_type_dispatch import body_terminates
44
45
 
45
46
 
46
47
  if TYPE_CHECKING:
@@ -81,6 +82,7 @@ _WALRUS_CODES = frozenset({"SARJ081", "SARJ432"})
81
82
  _JWT_POSITIONAL_PREFIX_ARGUMENTS = 3
82
83
  _STREAM_MIN_BODY_LENGTH = 3
83
84
  _CONSTANT_TRUE_DEPTH_LIMIT = 32
85
+ _INERT_DEFAULT_NODE_LIMIT: Final = 128
84
86
  # Bound accepted revisions; each rule still owns its work and replacement size.
85
87
  _MAX_FIX_PASSES: Final = 256
86
88
  _PURE_STRING_TRANSFORMS: Final[dict[str, str]] = {
@@ -125,6 +127,9 @@ class _Source:
125
127
  self.export_locations = frozenset(
126
128
  (diagnostic.line, diagnostic.col) for diagnostic in diagnostics if diagnostic.code == "SARJ098"
127
129
  )
130
+ self.test_comment_locations = frozenset(
131
+ (diagnostic.line, diagnostic.col) for diagnostic in diagnostics if diagnostic.code == "SARJ551"
132
+ )
128
133
 
129
134
  @cached_property
130
135
  def enums(self) -> EnumFixes:
@@ -140,7 +145,7 @@ class _Source:
140
145
 
141
146
  @cached_property
142
147
  def commentary(self) -> CommentFixes:
143
- return CommentFixes(self.context)
148
+ return CommentFixes(self.context, test_comment_locations=self.test_comment_locations)
144
149
 
145
150
  @cached_property
146
151
  def declared_contracts(self) -> DeclaredContractFixes:
@@ -460,6 +465,10 @@ def _sql_projection_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdi
460
465
  return sql_projection_fixes(source.context, diagnostic)
461
466
 
462
467
 
468
+ def _sql_alias_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdit]:
469
+ return sql_alias_fixes(source.context, diagnostic)
470
+
471
+
463
472
  def _alias_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdit]:
464
473
  if not diagnostic.fixes:
465
474
  return source.aliases.fixes(diagnostic)
@@ -767,9 +776,15 @@ def _dispatch_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdit]:
767
776
  if diagnostic.code == "SARJ080" and diagnostic.severity is not Severity.ERROR:
768
777
  return []
769
778
  for root in source.context.nodes(ast.If):
770
- if not _at(root, diagnostic) or source.comments_between(root.lineno, _last_line(root)):
779
+ if not _at(root, diagnostic):
771
780
  continue
772
- matched = _dispatch_match(root, source.context, type_dispatch=diagnostic.code == "SARJ080")
781
+ type_dispatch = diagnostic.code == "SARJ080"
782
+ roots = _dispatch_roots(root, source.context) if type_dispatch else [root]
783
+ if source.comments_between(root.lineno, _last_line(roots[-1])) or (
784
+ len(roots) > 1 and any(source.full_statement(branch) is None for branch in roots)
785
+ ):
786
+ continue
787
+ matched = _dispatch_match(roots, source.context, type_dispatch=type_dispatch)
773
788
  if matched is None or not isinstance(matched.subject, ast.Name):
774
789
  continue
775
790
  if not _dispatch_parameter(root, matched.subject, source):
@@ -777,15 +792,41 @@ def _dispatch_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdit]:
777
792
  rendered = ast.unparse(matched)
778
793
  indent = source.lines[root.lineno - 1][: root.col_offset]
779
794
  newline = "\r\n" if "\r\n" in source.text else "\n"
780
- return [source.replace(root, (newline + indent).join(rendered.splitlines()))]
795
+ return [
796
+ TextEdit(
797
+ source.span(root).start, source.span(roots[-1]).end, (newline + indent).join(rendered.splitlines())
798
+ )
799
+ ]
781
800
  return []
782
801
 
783
802
 
784
- def _dispatch_match(root: ast.If, context: PythonFileContext, *, type_dispatch: bool) -> ast.Match | None:
803
+ def _dispatch_roots(root: ast.If, context: PythonFileContext) -> list[ast.If]:
804
+ if root.orelse:
805
+ return [root]
806
+ parent = context.parents.get(root)
807
+ for field in ("body", "orelse", "finalbody"):
808
+ value: object = getattr( # sarj-noqa: SARJ557 -- Inspect optional statement-list fields across heterogeneous AST node types.
809
+ parent, field, None
810
+ )
811
+ if not object_list(value) or root not in value:
812
+ continue
813
+ branches: list[ast.If] = []
814
+ for statement in value[value.index(root) :]:
815
+ if isinstance(statement, ast.If) and statement.orelse:
816
+ return [root]
817
+ if not isinstance(statement, ast.If) or not body_terminates(statement.body):
818
+ break
819
+ branches.append(statement)
820
+ return branches or [root]
821
+ return [root]
822
+
823
+
824
+ def _dispatch_match(roots: Sequence[ast.If], context: PythonFileContext, *, type_dispatch: bool) -> ast.Match | None:
785
825
  cases: list[ast.match_case] = []
786
- branch = root
826
+ pending = list(reversed(roots))
787
827
  subject: ast.Name | None = None
788
- while True:
828
+ while pending:
829
+ branch = pending.pop()
789
830
  if type_dispatch and _type_parameter_reference(branch.test, context):
790
831
  return None
791
832
  pattern = _dispatch_pattern(branch.test, type_dispatch=type_dispatch)
@@ -794,11 +835,12 @@ def _dispatch_match(root: ast.If, context: PythonFileContext, *, type_dispatch:
794
835
  subject = pattern[0]
795
836
  cases.append(ast.match_case(pattern=pattern[1], guard=None, body=branch.body))
796
837
  if len(branch.orelse) == 1 and isinstance(branch.orelse[0], ast.If):
797
- branch = branch.orelse[0]
798
- continue
799
- if branch.orelse:
838
+ pending.append(branch.orelse[0])
839
+ elif branch.orelse:
800
840
  cases.append(ast.match_case(pattern=ast.MatchAs(), guard=None, body=branch.orelse))
801
- return ast.Match(subject=subject, cases=cases)
841
+ if subject is None or (type_dispatch and len(cases) == 1):
842
+ return None
843
+ return ast.Match(subject=subject, cases=cases)
802
844
 
803
845
 
804
846
  def _dispatch_pattern(test: ast.expr, *, type_dispatch: bool) -> tuple[ast.Name, ast.pattern] | None:
@@ -1233,32 +1275,79 @@ def _literal_collection_element(element: ast.expr, names: frozenset[str]) -> boo
1233
1275
  return isinstance(element, ast.Constant) or (isinstance(element, ast.Name) and element.id in names)
1234
1276
 
1235
1277
 
1236
- def _inert_definition(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
1278
+ def _inert_definition(node: ast.FunctionDef | ast.AsyncFunctionDef, *, postponed_annotations: bool) -> bool:
1237
1279
  args = node.args
1238
1280
  if node.decorator_list or node.type_params:
1239
1281
  return False
1240
1282
  return not (
1241
- args.defaults
1242
- or any(value is not None for value in args.kw_defaults)
1243
- or node.returns is not None
1244
- or any(
1245
- argument.annotation is not None
1246
- for argument in (*args.posonlyargs, *args.args, *args.kwonlyargs, args.vararg, args.kwarg)
1247
- if argument is not None
1283
+ any(not _inert_default(value) for value in args.defaults)
1284
+ or any(value is not None and not _inert_default(value) for value in args.kw_defaults)
1285
+ or (
1286
+ not postponed_annotations
1287
+ and (
1288
+ not _inert_annotation(node.returns)
1289
+ or any(
1290
+ not _inert_annotation(argument.annotation)
1291
+ for argument in (*args.posonlyargs, *args.args, *args.kwonlyargs, args.vararg, args.kwarg)
1292
+ if argument is not None
1293
+ )
1294
+ )
1248
1295
  )
1249
1296
  )
1250
1297
 
1251
1298
 
1299
+ def _inert_default(value: ast.expr) -> bool:
1300
+ if isinstance(value, ast.Constant):
1301
+ return True
1302
+ pending: list[tuple[ast.expr, bool]] = [(value, False)]
1303
+ remaining = _INERT_DEFAULT_NODE_LIMIT
1304
+ while pending:
1305
+ if not remaining:
1306
+ return False
1307
+ expression, numeric_required = pending.pop()
1308
+ remaining -= 1
1309
+ match expression:
1310
+ case ast.Constant(value=literal):
1311
+ literal_type = type(literal)
1312
+ if (
1313
+ numeric_required
1314
+ and literal_type is not int
1315
+ and literal_type is not float
1316
+ and literal_type is not complex
1317
+ ):
1318
+ return False
1319
+ continue
1320
+ case ast.Tuple(elts=elements) if not numeric_required:
1321
+ children: Sequence[ast.expr] = elements
1322
+ child_numeric_required = False
1323
+ case ast.UnaryOp(op=ast.UAdd() | ast.USub(), operand=operand):
1324
+ children = (operand,)
1325
+ child_numeric_required = True
1326
+ case _:
1327
+ return False
1328
+ if len(children) + len(pending) > remaining:
1329
+ return False
1330
+ pending.extend((child, child_numeric_required) for child in children)
1331
+ return True
1332
+
1333
+
1334
+ def _inert_annotation(annotation: ast.expr | None) -> bool:
1335
+ return annotation is None or (
1336
+ isinstance(annotation, ast.Constant) and (annotation.value is None or isinstance(annotation.value, str))
1337
+ )
1338
+
1339
+
1252
1340
  def _stepdown_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdit]:
1253
1341
  tree = source.context.tree
1254
1342
  if tree is None:
1255
1343
  return []
1344
+ postponed_annotations = _postponed_module_annotations(tree) or bool(source.context.nodes(ast.TemplateStr))
1256
1345
  for index, helper in enumerate(tree.body):
1257
1346
  if not isinstance(helper, (ast.FunctionDef, ast.AsyncFunctionDef)) or not _at(helper, diagnostic):
1258
1347
  continue
1259
- if not _inert_definition(helper):
1348
+ if not _inert_definition(helper, postponed_annotations=postponed_annotations):
1260
1349
  continue
1261
- caller = _inert_stepdown_caller(helper, tree.body[index + 1 :])
1350
+ caller = _inert_stepdown_caller(helper, tree.body[index + 1 :], postponed_annotations=postponed_annotations)
1262
1351
  if caller is None or _stepdown_comments(helper, caller, source):
1263
1352
  continue
1264
1353
  span = source.full_statement(helper)
@@ -1270,20 +1359,37 @@ def _stepdown_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdit]:
1270
1359
  return []
1271
1360
 
1272
1361
 
1362
+ def _postponed_module_annotations(tree: ast.Module) -> bool:
1363
+ for index, statement in enumerate(tree.body):
1364
+ if (
1365
+ index == 0
1366
+ and isinstance(statement, ast.Expr)
1367
+ and isinstance(statement.value, ast.Constant)
1368
+ and isinstance(statement.value.value, str)
1369
+ ):
1370
+ continue
1371
+ if not isinstance(statement, ast.ImportFrom) or statement.module != "__future__" or statement.level != 0:
1372
+ return False
1373
+ if any(alias.name == "annotations" for alias in statement.names):
1374
+ return True
1375
+ return False
1376
+
1377
+
1273
1378
  def _inert_stepdown_caller(
1274
- helper: ast.FunctionDef | ast.AsyncFunctionDef, later: list[ast.stmt]
1379
+ helper: ast.FunctionDef | ast.AsyncFunctionDef, later: list[ast.stmt], *, postponed_annotations: bool
1275
1380
  ) -> ast.FunctionDef | ast.AsyncFunctionDef | None:
1276
1381
  callers = [
1277
1382
  node
1278
1383
  for node in later
1279
1384
  if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and _calls_function(node, helper.name)
1280
1385
  ]
1281
- if len(callers) != 1 or not _inert_definition(callers[0]):
1386
+ if len(callers) != 1 or not _inert_definition(callers[0], postponed_annotations=postponed_annotations):
1282
1387
  return None
1283
1388
  caller = callers[0]
1284
1389
  for node in later:
1285
1390
  if node.lineno <= caller.lineno and (
1286
- not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) or not _inert_definition(node)
1391
+ not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
1392
+ or not _inert_definition(node, postponed_annotations=postponed_annotations)
1287
1393
  ):
1288
1394
  return None
1289
1395
  return caller
@@ -1322,6 +1428,7 @@ def _parameter_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdit]:
1322
1428
 
1323
1429
  _BUILDERS: Final[dict[str, Callable[[_Source, Diagnostic], list[TextEdit]]]] = {
1324
1430
  "SARJ021": _sql_projection_fix,
1431
+ "SARJ543": _sql_alias_fix,
1325
1432
  "SARJ002": _literal_loop_fix,
1326
1433
  "SARJ006": _enum_fix,
1327
1434
  "SARJ011": _security_fix,
@@ -1332,7 +1439,7 @@ _BUILDERS: Final[dict[str, Callable[[_Source, Diagnostic], list[TextEdit]]]] = {
1332
1439
  "SARJ024": _repeated_fix,
1333
1440
  "SARJ032": _exhaustiveness_fix,
1334
1441
  "SARJ046": _xfail_fix,
1335
- **dict.fromkeys({"SARJ049", "SARJ051", "SARJ054", "SARJ434"}, _commentary_fix),
1442
+ **dict.fromkeys({"SARJ049", "SARJ051", "SARJ054", "SARJ434", "SARJ551"}, _commentary_fix),
1336
1443
  "SARJ067": _mock_fix,
1337
1444
  "SARJ068": _fstring_fix,
1338
1445
  "SARJ070": _pattern_fix,
@@ -1378,7 +1485,7 @@ class _FixPass:
1378
1485
 
1379
1486
 
1380
1487
  def fix_source(path: Path, source: str, *, rule_ids: Sequence[str] | None = None) -> str:
1381
- from sarj_python_lint.__main__ import check_source # ruff: ignore[import-outside-top-level] -- the CLI imports this entry point lazily.
1488
+ from sarj_python_lint.__main__ import check_context # ruff: ignore[import-outside-top-level] -- the CLI imports this entry point lazily.
1382
1489
 
1383
1490
  selected = list(REGISTRY) if rule_ids is None else list(rule_ids)
1384
1491
  rules = [REGISTRY[rule_id]() for rule_id in selected if REGISTRY[rule_id].code in _BUILDERS]
@@ -1394,16 +1501,13 @@ def fix_source(path: Path, source: str, *, rule_ids: Sequence[str] | None = None
1394
1501
  while (fingerprint := sha256(current.encode("utf-8")).digest()) not in seen:
1395
1502
  seen.add(fingerprint)
1396
1503
  context = PythonFileContext(path, current)
1397
- diagnostics = check_source(rules, path, current)
1504
+ diagnostics = check_context(rules, context)
1398
1505
  candidates = _candidate_edits(context, diagnostics)
1399
1506
  if not candidates:
1400
1507
  return current
1401
1508
  # Batch independent candidates; overlapping candidates get fresh offsets on the next pass.
1402
1509
  selected_pass = _coherent_edits(candidates)
1403
- updated = apply_edits(current, selected_pass.edits)
1404
- compile( # sarj-noqa: SARJ557 -- Compile proposed fixes to reject invalid scopes without executing them.
1405
- updated, str(path), "exec", dont_inherit=True
1406
- )
1510
+ updated = _apply_validated_edits(path, current, selected_pass.edits)
1407
1511
  if updated == current:
1408
1512
  return current
1409
1513
  if passes == _MAX_FIX_PASSES:
@@ -1414,6 +1518,23 @@ def fix_source(path: Path, source: str, *, rule_ids: Sequence[str] | None = None
1414
1518
  raise _convergence_error(path, reason="source cycle", passes=passes, codes=applied_codes)
1415
1519
 
1416
1520
 
1521
+ def _apply_validated_edits(path: Path, current: str, edits: Sequence[TextEdit]) -> str:
1522
+ updated = apply_edits(current, edits)
1523
+ try:
1524
+ compile( # sarj-noqa: SARJ557 -- Compile proposed fixes to reject invalid scopes without executing them.
1525
+ updated, str(path), "exec", dont_inherit=True
1526
+ )
1527
+ except SyntaxError:
1528
+ try:
1529
+ compile( # sarj-noqa: SARJ557 -- Distinguish pre-existing invalid scope from a broken generated fix without executing input.
1530
+ current, str(path), "exec", dont_inherit=True
1531
+ )
1532
+ except SyntaxError:
1533
+ return current
1534
+ raise
1535
+ return updated
1536
+
1537
+
1417
1538
  def _convergence_error(
1418
1539
  path: Path, *, reason: Literal["pass budget exhausted", "source cycle"], passes: int, codes: set[str]
1419
1540
  ) -> ValueError:
@@ -1423,6 +1544,8 @@ def _convergence_error(
1423
1544
 
1424
1545
 
1425
1546
  def _candidate_edits(context: PythonFileContext, diagnostics: Sequence[Diagnostic]) -> list[_FixCandidate]:
1547
+ if not diagnostics:
1548
+ return []
1426
1549
  view = _Source(context, diagnostics)
1427
1550
  return [
1428
1551
  _FixCandidate(diagnostic.code, tuple(edits))
@@ -24,13 +24,18 @@ _UNUSED_API_DIRECTIVE = re.compile(
24
24
  r"#\s*ruff:\s*(?:noqa\s*:\s*TID251|file-ignore\s*\[\s*(?:TID251|banned-api)\s*\])\s*\Z",
25
25
  re.IGNORECASE,
26
26
  )
27
- _MIN_PARAGRAPH_LINES = 4
28
27
 
29
28
 
30
29
  @final
31
30
  class CommentFixes:
32
- def __init__(self, context: PythonFileContext) -> None:
31
+ def __init__(
32
+ self,
33
+ context: PythonFileContext,
34
+ *,
35
+ test_comment_locations: frozenset[tuple[int, int]] = frozenset(),
36
+ ) -> None:
33
37
  self.context = context
38
+ self.test_comment_locations = test_comment_locations
34
39
  self.lines = StringIO(context.source, newline="").readlines()
35
40
  self.starts = [0]
36
41
  for line in self.lines:
@@ -57,8 +62,12 @@ class CommentFixes:
57
62
  if diagnostic.code == "SARJ054" and self._unused_api_directive(token):
58
63
  return [self._remove(token)]
59
64
  if diagnostic.code == "SARJ434" and diagnostic.severity is Severity.ERROR:
60
- paragraph = self._restates_accumulation(token)
65
+ paragraph = self._restates_narration(token)
61
66
  return [self._remove(comment) for comment in paragraph]
67
+ if diagnostic.code == "SARJ551":
68
+ paragraph = self._restates_narration(token)
69
+ if all((comment.start[0], comment.start[1] + 1) in self.test_comment_locations for comment in paragraph):
70
+ return [self._remove(comment) for comment in paragraph]
62
71
  return []
63
72
 
64
73
  def _unused_api_directive(self, comment: tokenize.TokenInfo) -> bool:
@@ -114,18 +123,14 @@ class CommentFixes:
114
123
  case _:
115
124
  return False
116
125
 
117
- def _restates_accumulation(self, first: tokenize.TokenInfo) -> list[tokenize.TokenInfo]:
126
+ def _restates_narration(self, first: tokenize.TokenInfo) -> list[tokenize.TokenInfo]:
118
127
  group = self.prose_groups.get((first.start[0], first.start[1] + 1))
119
128
  if group is None:
120
129
  return []
121
130
  if self.narration is None:
122
131
  self.narration = CommentNarration(self.context)
123
132
  matched = self.narration.match(group)
124
- if (
125
- matched is not None
126
- and matched.kind == "accumulation"
127
- and len(matched.comment_lines) >= _MIN_PARAGRAPH_LINES
128
- ):
133
+ if matched is not None:
129
134
  return [self.comments[line] for line in matched.comment_lines]
130
135
  return []
131
136
 
@@ -0,0 +1,223 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from io import StringIO
5
+ import re
6
+ from typing import TYPE_CHECKING
7
+
8
+ from sarj_rule_contracts import TextEdit, apply_edits
9
+ from sqlglot import exp, parse_one
10
+ from sqlglot.errors import ParseError, TokenError
11
+
12
+ from sarj_python_lint._closed_sql_literal import (
13
+ has_comments as _has_comments,
14
+ literal_query as _literal_query,
15
+ owned_literal as _owned_literal,
16
+ )
17
+ from sarj_python_lint.rules._ast_index import object_list
18
+ from sarj_python_lint.rules._store_sql import sql_child, sql_children
19
+ from sarj_python_lint.rules.no_redundant_sql_alias import identifier_key
20
+
21
+
22
+ if TYPE_CHECKING:
23
+ import ast
24
+
25
+ from sarj_python_lint._file_context import PythonFileContext
26
+ from sarj_python_lint.rule_base import Diagnostic
27
+
28
+
29
+ _QUERY = re.compile(
30
+ r"[ \t]*SELECT[ \t]+(?P<star>\*)[ \t]+FROM[ \t]+"
31
+ r"\([ \t]*VALUES[ \t]+\([ \t]*(?P<values>[0-9]{1,9}(?:[ \t]*,[ \t]*[0-9]{1,9}){0,7})"
32
+ r'[ \t]*\)[ \t]*\)[ \t]+AS[ \t]+(?P<alias>"[A-Za-z_][A-Za-z0-9_]{0,62}")[ \t]*'
33
+ r'\([ \t]*(?P<labels>"[A-Za-z_][A-Za-z0-9_]{0,62}"'
34
+ r'(?:[ \t]*,[ \t]*"[A-Za-z_][A-Za-z0-9_]{0,62}"){0,7})[ \t]*\)'
35
+ r"[ \t]*;?[ \t]*\Z",
36
+ re.IGNORECASE | re.ASCII,
37
+ )
38
+ _QUERY_LIMIT = 2048
39
+ _SQL_IDENTIFIER = r'(?:"[A-Za-z_][A-Za-z0-9_]{0,62}"|[A-Za-z_][A-Za-z0-9_]{0,62})'
40
+ _INTEGER_LITERAL = re.compile(r"[0-9]{1,9}\Z", re.ASCII)
41
+ _VALUES_ROW = r"\([ \t]*[0-9]{1,9}(?:[ \t]*,[ \t]*[0-9]{1,9}){0,7}[ \t]*\)"
42
+ _VALUES_BOUND = 8
43
+ _SELF_ALIAS_COLUMN = re.compile(
44
+ rf"(?P<column>{_SQL_IDENTIFIER}\.{_SQL_IDENTIFIER})(?P<alias>[ \t]+AS[ \t]+{_SQL_IDENTIFIER})?",
45
+ re.IGNORECASE | re.ASCII,
46
+ )
47
+ _SELF_ALIAS_QUERY = re.compile(
48
+ rf"[ \t]*SELECT[ \t]+(?P<columns>{_SELF_ALIAS_COLUMN.pattern}"
49
+ rf"(?:[ \t]*,[ \t]*{_SQL_IDENTIFIER}\.{_SQL_IDENTIFIER}(?:[ \t]+AS[ \t]+{_SQL_IDENTIFIER})?){{0,7}})"
50
+ r"[ \t]+FROM[ \t]+\([ \t]*VALUES[ \t]+"
51
+ rf"{_VALUES_ROW}(?:[ \t]*,[ \t]*{_VALUES_ROW}){{0,7}}[ \t]*\)"
52
+ rf"[ \t]+AS[ \t]+{_SQL_IDENTIFIER}[ \t]*\([ \t]*"
53
+ rf"{_SQL_IDENTIFIER}(?:[ \t]*,[ \t]*{_SQL_IDENTIFIER}){{0,7}}[ \t]*\)[ \t]*;?[ \t]*\Z",
54
+ re.IGNORECASE | re.ASCII,
55
+ )
56
+
57
+
58
+ @dataclass(frozen=True, slots=True)
59
+ class _Projection:
60
+ offset: int
61
+ replacement: str
62
+
63
+
64
+ def fixes(context: PythonFileContext, diagnostic: Diagnostic) -> list[TextEdit]:
65
+ tree = context.tree
66
+ if tree is None or context.generated or _has_comments(context.source):
67
+ return []
68
+ literal = _owned_literal(tree)
69
+ if literal is None or (literal.lineno, literal.col_offset + 1) != (diagnostic.line, diagnostic.col):
70
+ return []
71
+ query = _literal_query(context.source, literal)
72
+ projection = _projection(query) if query is not None else None
73
+ if projection is None:
74
+ return []
75
+ start = _literal_offset(context.source, literal) + projection.offset
76
+ return [TextEdit(start, start + 1, projection.replacement)]
77
+
78
+
79
+ def alias_fixes(context: PythonFileContext, diagnostic: Diagnostic) -> list[TextEdit]:
80
+ tree = context.tree
81
+ if tree is None or context.generated or _has_comments(context.source):
82
+ return []
83
+ literal = _owned_literal(tree)
84
+ if literal is None or (literal.lineno, literal.col_offset + 1) != (diagnostic.line, diagnostic.col):
85
+ return []
86
+ query = _literal_query(context.source, literal)
87
+ if query is None:
88
+ return []
89
+ start = _literal_offset(context.source, literal)
90
+ return [TextEdit(start + edit.start, start + edit.end, edit.replacement) for edit in _alias_edits(query)]
91
+
92
+
93
+ def _literal_offset(source: str, literal: ast.Constant) -> int:
94
+ lines = StringIO(source, newline="").readlines()
95
+ prefix = lines[literal.lineno - 1].encode("utf-8")[: literal.col_offset].decode("utf-8")
96
+ return sum(map(len, lines[: literal.lineno - 1])) + len(prefix) + 1
97
+
98
+
99
+ def _alias_edits(query: str) -> list[TextEdit]:
100
+ if len(query) > _QUERY_LIMIT or (matched := _SELF_ALIAS_QUERY.fullmatch(query)) is None:
101
+ return []
102
+ offset = matched.start("columns")
103
+ edits = [
104
+ TextEdit(offset + part.start("alias"), offset + part.end("alias"), "")
105
+ for part in _SELF_ALIAS_COLUMN.finditer(matched["columns"])
106
+ if part.group("alias") is not None
107
+ ]
108
+ if not edits:
109
+ return []
110
+ candidate = apply_edits(query, edits)
111
+ return edits if _alias_delta(query, candidate) else []
112
+
113
+
114
+ def _alias_delta(original: str, candidate: str) -> bool:
115
+ try:
116
+ before = parse_one(original, read="postgres")
117
+ after = parse_one(candidate, read="postgres")
118
+ except ParseError, TokenError:
119
+ return False
120
+ if not isinstance(before, exp.Select) or not isinstance(after, exp.Select):
121
+ return False
122
+ columns = _self_alias_columns(before)
123
+ if columns is None:
124
+ return False
125
+ expected = before.copy()
126
+ expected.set("expressions", [part.copy() for part in columns])
127
+ return expected == after
128
+
129
+
130
+ def _self_alias_columns(statement: exp.Select) -> list[exp.Column] | None:
131
+ from_clause = sql_child(statement, "from_")
132
+ relation = sql_child(from_clause, "this") if isinstance(from_clause, exp.From) else None
133
+ alias = sql_child(relation, "alias") if isinstance(relation, exp.Values) else None
134
+ if not isinstance(alias, exp.TableAlias):
135
+ return None
136
+ qualifier = identifier_key(sql_child(alias, "this"))
137
+ labels = [identifier_key(part) for part in sql_children(alias, "columns")]
138
+ if (
139
+ qualifier is None
140
+ or None in labels
141
+ or len(set(labels)) != len(labels)
142
+ or not isinstance(relation, exp.Values)
143
+ or not _literal_rows(relation, len(labels))
144
+ ):
145
+ return None
146
+ columns: list[exp.Column] = []
147
+ for expression in sql_children(statement, "expressions"):
148
+ column = _self_alias_column(expression)
149
+ if (
150
+ column is None
151
+ or identifier_key(sql_child(column, "table")) != qualifier
152
+ or identifier_key(sql_child(column, "this")) not in labels
153
+ ):
154
+ return None
155
+ columns.append(column)
156
+ return columns
157
+
158
+
159
+ def _literal_rows(relation: exp.Values, width: int) -> bool:
160
+ rows = sql_children(relation, "expressions")
161
+ return (
162
+ 1 <= width <= _VALUES_BOUND
163
+ and 1 <= len(rows) <= _VALUES_BOUND
164
+ and all(isinstance(row, exp.Tuple) and _literal_row(row, width) for row in rows)
165
+ )
166
+
167
+
168
+ def _literal_row(row: exp.Tuple, width: int) -> bool:
169
+ values = sql_children(row, "expressions")
170
+ return len(values) == width and all(
171
+ isinstance(value, exp.Literal) and not value.is_string and _INTEGER_LITERAL.fullmatch(value.name) is not None
172
+ for value in values
173
+ )
174
+
175
+
176
+ def _self_alias_column(expression: exp.Expr) -> exp.Column | None:
177
+ if isinstance(expression, exp.Column):
178
+ return expression
179
+ column = sql_child(expression, "this") if isinstance(expression, exp.Alias) else None
180
+ if not isinstance(column, exp.Column):
181
+ return None
182
+ original = identifier_key(sql_child(column, "this"))
183
+ return column if original is not None and original == identifier_key(sql_child(expression, "alias")) else None
184
+
185
+
186
+ def _projection(query: str) -> _Projection | None:
187
+ if len(query) > _QUERY_LIMIT or (matched := _QUERY.fullmatch(query)) is None:
188
+ return None
189
+ labels = [part.strip() for part in matched["labels"].split(",")]
190
+ if len(labels) != len(matched["values"].split(",")) or len(set(labels)) != len(labels):
191
+ return None
192
+ replacement = ", ".join(f"{matched['alias']}.{label}" for label in labels)
193
+ candidate = query[: matched.start("star")] + replacement + query[matched.end("star") :]
194
+ if not _projection_delta(query, candidate):
195
+ return None
196
+ return _Projection(offset=matched.start("star"), replacement=replacement)
197
+
198
+
199
+ def _projection_delta(original: str, candidate: str) -> bool:
200
+ try:
201
+ before = parse_one(original, read="postgres")
202
+ after = parse_one(candidate, read="postgres")
203
+ except ParseError, TokenError:
204
+ return False
205
+ if not isinstance(before, exp.Select) or not isinstance(after, exp.Select) or before.expressions != [exp.Star()]:
206
+ return False
207
+ columns = _projection_columns(after.expressions)
208
+ if columns is None:
209
+ return False
210
+ expected = before.copy()
211
+ expected.set("expressions", [part.copy() for part in columns])
212
+ return expected == after
213
+
214
+
215
+ def _projection_columns(value: object) -> list[exp.Column] | None:
216
+ if not object_list(value):
217
+ return None
218
+ columns: list[exp.Column] = []
219
+ for part in value:
220
+ if not isinstance(part, exp.Column):
221
+ return None
222
+ columns.append(part)
223
+ return columns